Spring code skills that you can’t put down

Time:2021-8-24

preface

Recently, more and more readers recognize my article, which is still a very happy thing. Some readers wrote privately that I hope to share more articles on spring later, which can be used in practical work. I happen to have done some research on the spring source code. Combined with my practical work experience in recent years, I summarize the knowledge points I think are good in spring. I hope it will be helpful to you.

How to get the spring container object

1. Implement beanfactoryaware interface

`@Service`
`public class PersonService implements BeanFactoryAware {`
 `private BeanFactory beanFactory;`
 `@Override`
 `public void setBeanFactory(BeanFactory beanFactory) throws BeansException {`
 `this.beanFactory = beanFactory;`
 `}`
 `public void add() {`
 `Person person = (Person) beanFactory.getBean("person");`
 `}`
`}`
`Copy code`

realizationBeanFactoryAwareInterface, and then overridesetBeanFactoryMethod, you can get the spring container object from this method.

2. Implement applicationcontextaware interface

`@Service`
`public class PersonService2 implements ApplicationContextAware {`
 `private ApplicationContext applicationContext;`
 `@Override`
 `public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {`
 `this.applicationContext = applicationContext;`
 `}`
 `public void add() {`
 `Person person = (Person) applicationContext.getBean("person");`
 `}`
`}`
`Copy code`

realizationApplicationContextAwareInterface, and then overridesetApplicationContextMethod, you can also get the spring container object from this method.

3. Implement the applicationlistener interface

`@Service`
`public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {`
 `private ApplicationContext applicationContext;`
 `@Override`
 `public void onApplicationEvent(ContextRefreshedEvent event) {`
 `applicationContext = event.getApplicationContext();`
 `}`
 `public void add() {`
 `Person person = (Person) applicationContext.getBean("person");`
 `}`
`}`
`Copy code`

realizationApplicationListenerInterface. Note that the generic type received by this interface isContextRefreshedEventClass, and then overrideonApplicationEventMethod, you can also get the spring container object from this method.

In addition, I have to mentionAwareInterface, which is actually an empty interface without any methods.

It indicates the perceived meaning. The specified object can be obtained through this kind of interface, such as:

  • Obtain beanfactory through beanfactory aware
  • Get ApplicationContext through applicationcontextaware
  • Obtain beanname, etc. through beannaaware

AwareInterface is a very common function. At present, it includes the following functions:

Spring code skills that you can't put down

Second, how to initialize beans

Spring supports three methods of initializing beans:

  • Specify init method method in XML
  • Use @ postconstruct annotation
  • Implement the initializingbean interface

The first method is too old. Not many people use it now. I won’t introduce the specific usage.

1. Use @ postconstruct annotation

`@Service`
`public class AService {`
 `@PostConstruct`
 `public void init() {`
 `System. Out. Println ("= = = initialization = = =")`
 `}`
`}`
`Copy code`

Add on the method that needs to be initialized@PostConstructAnnotation, so it has the ability of initialization.

2. Implement initializingbean interface

`@Service`
`public class BService implements InitializingBean {`
 `@Override`
 `public void afterPropertiesSet() throws Exception {`
 `System. Out. Println ("= = = initialization = = =")`
 `}`
`}`
`Copy code`

realizationInitializingBeanInterfaces, rewritingafterPropertiesSetMethod, in which the initialization function can be completed.

Here is an interesting question:init-methodPostConstructandInitializingBeanWhat is the order of execution?

The key code that determines their call order isAbstractAutowireCapableBeanFactoryClassinitializeBeanMethod.

Spring code skills that you can't put down

This code will be called firstPostprocessbeforeinitialization of beanpostprocessorMethod, andPostConstructYesInitDestroyAnnotationBeanPostProcessorYes, it isBeanPostProcessor, soPostConstructExecute first.

andinvokeInitMethodsCode in method:Spring code skills that you can't put down

Decided to call firstInitializingBean, and then callinit-method

Therefore, it is concluded that their calling order is:Spring code skills that you can't put down

III. customize your own scope

We all know that spring supports by defaultScopeThere are only two:

  • Singleton singleton, the bean obtained from the spring container is the same object every time.
  • There are multiple instances of prototype. Each time the bean obtained from the spring container is a different object.

Spring web extends scope by adding:

  • Requestscope all the beans obtained from the spring container in the same request are the same object.
  • Sessionscope all the beans obtained from the spring container in the same session are the same object.

Even so, some scenes still can not meet our requirements.

For example, what should we do if we want all the beans obtained from the spring container in the same thread to be the same object?

This requires a custom scope.

Step 1: implement the scope interface:

`public class ThreadLocalScope implements Scope {`
 `private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();`
 `@Override`
 `public Object get(String name, ObjectFactory<?> objectFactory) {`
 `Object value = THREAD_LOCAL_SCOPE.get();`
 `if (value != null) {`
 `return value;`
 `}`
 `Object object = objectFactory.getObject();`
 `THREAD_LOCAL_SCOPE.set(object);`
 `return object;`
 `}`
 `@Override`
 `public Object remove(String name) {`
 `THREAD_LOCAL_SCOPE.remove();`
 `return null;`
 `}`
 `@Override`
 `public void registerDestructionCallback(String name, Runnable callback) {`
 `}`
 `@Override`
 `public Object resolveContextualObject(String key) {`
 `return null;`
 `}`
 `@Override`
 `public String getConversationId() {`
 `return null;`
 `}`
`}`
`Copy code`

Step 2: inject the newly defined scope into the spring container:

`@Component`
`public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {`
 `@Override`
 `public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {`
 `beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());`
 `}`
`}`
`Copy code`

Step 3: use the newly defined scope:

`@Scope("threadLocalScope")`
`@Service`
`public class CService {`
 `public void add() {`
 `}`
`}`
`Copy code`

Four, don’t say factorybean is useless

Speaking ofFactoryBeanI have to mentionBeanFactory, because interviewers always like to ask about their differences.

  • Beanfactory: the top-level interface of the spring container, which manages the bean factory.
  • Factorybean: it is not an ordinary factory bean. It hides the details of instantiating some complex beans and brings convenience to upper layer applications.

If you look at the spring source code, you will find that it uses the factorybean interface in more than 70 places.

Spring code skills that you can't put down

The above figure is enough to illustrate the importance of this interface. Do not ignore it, OK?

In particular:mybatisYesSqlSessionFactoryObject is throughSqlSessionFactoryBeanClass.

Let’s define our factorybean together:

`@Component`
`public class MyFactoryBean implements FactoryBean {`
 `@Override`
 `public Object getObject() throws Exception {`
 `String data1 = buildData1();`
 `String data2 = buildData2();`
 `return buildData3(data1, data2);`
 `}`
 `private String buildData1() {`
 `return "data1";`
 `}`
 `private String buildData2() {`
 `return "data2";`
 `}`
 `private String buildData3(String data1, String data2) {`
 `return data1 + data2;`
 `}`
 `@Override`
 `public Class<?> getObjectType() {`
 `return null;`
 `}`
`}`
`Copy code`

Get factorybean instance object:

`@Service`
`public class MyFactoryBeanService implements BeanFactoryAware {`
 `private BeanFactory beanFactory;`
 `@Override`
 `public void setBeanFactory(BeanFactory beanFactory) throws BeansException {`
 `this.beanFactory = beanFactory;`
 `}`
 `public void test() {`
 `Object myFactoryBean = beanFactory.getBean("myFactoryBean");`
 `System.out.println(myFactoryBean);`
 `Object myFactoryBean1 = beanFactory.getBean("&myFactoryBean");`
 `System.out.println(myFactoryBean1);`
 `}`
`}`
`Copy code`
  • getBean("myFactoryBean");What you get isMyFactoryBeanServiceIn classgetObjectMethod,
  • getBean("&myFactoryBean");What you get isMyFactoryBeanObject.

V. easy custom type conversion

Spring currently supports 3 types of converters:

  • Converter < s, t >: converts an S-type object to a T-type object
  • Converterfactory < s, R >: converts S-type objects to R-type and subclass objects
  • Genericconverter: it supports the conversion of multiple source and target types. At the same time, it also provides the context of source and target types. This context allows you to realize type conversion based on annotations or information on attributes.

The scenarios used by these three types of converters are differentConverter<S,T>For example. If: in the entity object receiving parameters in the interface, the type of a field is date, but the actual parameter is a string type: 2021-01-03 10:20:15, how to deal with it?

Step 1: define an entity user:

`@Data`
`public class User {`
 `private Long id;`
 `private String name;`
 `private Date registerDate;`
`}`
`Copy code`

Step 2: implement the converter interface:

`public class DateConverter implements Converter<String, Date> {`
 `private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");`
 `@Override`
 `public Date convert(String source) {`
 `if (source != null && !"".equals(source)) {`
 `try {`
 `simpleDateFormat.parse(source);`
 `} catch (ParseException e) {`
 `e.printStackTrace();`
 `}`
 `}`
 `return null;`
 `}`
`}`
`Copy code`

Step 3: inject the newly defined type converter into the spring container:

`@Configuration`
`public class WebConfig extends WebMvcConfigurerAdapter {`
 `@Override`
 `public void addFormatters(FormatterRegistry registry) {`
 `registry.addConverter(new DateConverter());`
 `}`
`}`
`Copy code`

Step 4: call the interface

`@RequestMapping("/user")`
`@RestController`
`public class UserController {`
 `@RequestMapping("/save")`
 `public String save(@RequestBody User user) {`
 `return "success";`
 `}`
`}`
`Copy code`

When the interface is requested, the registerdate field in the user object will be automatically converted to date type.

Six spring MVC interceptors, all of which have been used

Compared with the spring interceptor, the spring MVC interceptor can obtainHttpServletRequestandHttpServletResponseAnd other web object instances.

The top-level interface of spring MVC interceptor is:HandlerInterceptor, including three methods:

  • Pre handle is executed before the target method is executed
  • Posthandle is executed after the target method is executed
  • Executed when the aftercompletion request is completed

We usually use it for convenienceHandlerInterceptorImplementation class of interfaceHandlerInterceptorAdapterClass.

If you have permission authentication, log and statistics scenarios, you can use this interceptor.

Step 1: inherit the handlerinterceptoradapter class and define the Interceptor:

`public class AuthInterceptor extends HandlerInterceptorAdapter {`
 `@Override`
 `public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)`
 `throws Exception {`
 `String requestUrl = request.getRequestURI();`
 `if (checkAuth(requestUrl)) {`
 `return true;`
 `}`
 `return false;`
 `}`
 `private boolean checkAuth(String requestUrl) {`
 `System. Out. Println ("= = permission verification = = =")`
 `return true;`
 `}`
`}`
`Copy code`

Step 2: register the interceptor with the spring container:

`@Configuration`
`public class WebAuthConfig extends WebMvcConfigurerAdapter {`
 
 `@Bean`
 `public AuthInterceptor getAuthInterceptor() {`
 `return new AuthInterceptor();`
 `}`
 `@Override`
 `public void addInterceptors(InterceptorRegistry registry) {`
 `registry.addInterceptor(getAuthInterceptor());`
 `}`
`}`
`Copy code`

Step 3: when requesting an interface, spring MVC can automatically intercept the interface and verify permissions through the interceptor.

In fact, the interceptor is relatively simple and can be used inDispatcherServletClassdoDispatchMethod:

Spring code skills that you can't put down

By the way, I only talk about spring MVC interceptors, not spring interceptors, because I’m a little selfish. I’ll know later.

Seven enable switches are really fragrant

I don’t know if you’ve ever used itEnableInitial annotations, such as enableasync, enablecaching, enableaspectjautoproxy, are like switches, as long as@ConfigurationAdd such annotations to the defined configuration class to enable related functions.

Isn’t it cool?

Let’s implement our own switch:

Step 1: define a logfilter:

`public class LogFilter implements Filter {`
 `@Override`
 `public void init(FilterConfig filterConfig) throws ServletException {`
 `}`
 `@Override`
 `public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {`
 `System. Out. Println ("record request log")`
 `chain.doFilter(request, response);`
 `System. Out. Println ("record response log")`
 `}`
 `@Override`
 `public void destroy() {`
 
 `}`
`}`
`Copy code`

Step 2: register logfilter:

`@ConditionalOnWebApplication`
`public class LogFilterWebConfig {`
 `@Bean`
 `public LogFilter timeFilter() {`
 `return new LogFilter();`
 `}`
`}`
`Copy code`

Note that it’s used here@ConditionalOnWebApplicationAnnotation, not used directly@ConfigurationNotes.

Step 3: define the switch @ enablelog annotation:

`@Target(ElementType.TYPE)`
`@Retention(RetentionPolicy.RUNTIME)`
`@Documented`
`@Import(LogFilterWebConfig.class)`
`public @interface EnableLog {`
`}`
`Copy code`

Step 4, justspringbootStart class plus@EnableLogAnnotation enables logfilter to record request and response logs.

VIII. Spring of resttemplate interceptor

We useRestTemplateWhen calling a remote interface, you sometimes need toheaderInformation, such as traceid, source, etc., is transmitted in the log, so that a complete request link can be connected in series when querying the log to quickly locate the problem.

This business scenario can be passedClientHttpRequestInterceptorInterface implementation, as follows:

The first step is to implement the clienthttprequeinterceptor interface:

`public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {`
 `@Override`
 `public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {`
 `request.getHeaders().set("traceId", MdcUtil.get());`
 `return execution.execute(request, body);`
 `}`
`}`
`Copy code`

Step 2: define the configuration class:

`@Configuration`
`public class RestTemplateConfiguration {`
 `@Bean`
 `public RestTemplate restTemplate() {`
 `RestTemplate restTemplate = new RestTemplate();`
 `restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor()));`
 `return restTemplate;`
 `}`
 `@Bean`
 `public RestTemplateInterceptor restTemplateInterceptor() {`
 `return new RestTemplateInterceptor();`
 `}`
`}`
`Copy code`

Mdcutil actually uses MDC tools inThreadLocalStore and get traceid in

`public class MdcUtil {`
 `private static final String TRACE_ID = "TRACE_ID";`
 `public static String get() {`
 `return MDC.get(TRACE_ID);`
 `}`
 `public static void add(String value) {`
 `MDC.put(TRACE_ID, value);`
 `}`
`}`
`Copy code`

Of course, this example does not demonstrate the specific calling of the add method of the mdcutil class. We can generate the traceid before executing the interface method in the filter, call the add method of the mdcutil class to add it to the MDC, and then obtain the traceid in other places of the same request through the get method of the mdcutil class.

IX. unified exception handling

In the past, when we developed the interface, if there were exceptions, in order to give users a more friendly prompt, for example:

`@RequestMapping("/test")`
`@RestController`
`public class TestController {`
 `@GetMapping("/add")`
 `public String add() {`
 `int a = 10 / 0;`
 `Return "success"`
 `}`
`}`
`Copy code`

If no processing is required, the add interface result will directly report an error:Spring code skills that you can't put down

what? Can users directly see the error message?

This interaction mode gives users a very poor experience. In order to solve this problem, we usually catch exceptions in the interface:

`@GetMapping("/add")`
`public String add() {`
 `String result = "success"`
 `try {`
 `int a = 10 / 0;`
 `} catch (Exception e) {`
 `Result = "data exception"`
 `}`
 `return result;`
`}`
`Copy code`

After the interface modification, when there is an exception, “data exception” will be prompted, which is more friendly to users.

It looks good, but there’s a problem…

If only one interface is OK, but if there are hundreds of interfaces in the project, do you need to add exception capture code?

If the answer is no, global exception handling will come in handy:RestControllerAdvice

`@RestControllerAdvice`
`public class GlobalExceptionHandler {`
 `@ExceptionHandler(Exception.class)`
 `public String handleException(Exception e) {`
 `if (e instanceof ArithmeticException) {`
 `Return "abnormal data"`
 `}`
 `if (e instanceof Exception) {`
 `Return "server internal exception"`
 `}`
 `retur nnull;`
 `}`
`}`
`Copy code`

JusthandleExceptionMethod, which can be safely used in the business interface, and there is no need to catch exceptions (someone handled them uniformly). It’s so refreshing.

Ten can be so elegant

In the past, when we used the asynchronous function, there were usually three ways:

  • Inherit thread class
  • Implement the runable interface
  • Use thread pool

Let’s review:

Inherit thread class

`public class MyThread extends Thread {`
 `@Override`
 `public void run() {`
 `System.out.println("===call MyThread===");`
 `}`
 `public static void main(String[] args) {`
 `new MyThread().start();`
 `}`
`}`
`Copy code`

Implement the runable interface

`public class MyWork implements Runnable {`
 `@Override`
 `public void run() {`
 `System.out.println("===call MyWork===");`
 `}`
 `public static void main(String[] args) {`
 `new Thread(new MyWork()).start();`
 `}`
`}`
`Copy code`

Use thread pool

`public class MyThreadPool {`
 `private static ExecutorService executorService = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200));`
 `static class Work implements Runnable {`
 `@Override`
 `public void run() {`
 `System.out.println("===call work===");`
 `}`
 `}`
 `public static void main(String[] args) {`
 `try {`
 `executorService.submit(new MyThreadPool.Work());`
 `} finally {`
 `executorService.shutdown();`
 `}`
 `}`
`}`
`Copy code`

These three methods of implementing asynchrony can’t be said bad, but spring has helped us extract some common places, so we don’t need to inheritThreadClass or implementationRunableInterface, it’s all done.

How to spring asynchronous function?

The first step is to add@EnableAsyncNotes.

`@EnableAsync`
`@SpringBootApplication`
`public class Application {`
 `public static void main(String[] args) {`
 `new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);`
 `}`
`}`
`Copy code`

Step 2: add @ async annotation to the methods that need to use asynchrony:

`@Service`
`public class PersonService {`
 `@Async`
 `public String get() {`
 `System.out.println("===add==");`
 `return "data";`
 `}`
`}`
`Copy code`

Then call personservice. Get () where it is used; It’s amazing to have asynchronous function.

By default, spring will create a thread for our asynchronous method to execute. If the method is called many times, a large number of threads need to be created, which will lead to a waste of resources.

At this time, we can define a thread pool, and the asynchronous methods will be automatically submitted to the thread pool for execution.

`@Configuration`
`public class ThreadPoolConfig {`
 `@Value("${thread.pool.corePoolSize:5}")`
 `private int corePoolSize;`
 `@Value("${thread.pool.maxPoolSize:10}")`
 `private int maxPoolSize;`
 `@Value("${thread.pool.queueCapacity:200}")`
 `private int queueCapacity;`
 `@Value("${thread.pool.keepAliveSeconds:30}")`
 `private int keepAliveSeconds;`
 `@Value("${thread.pool.threadNamePrefix:ASYNC_}")`
 `private String threadNamePrefix;`
 `@Bean`
 `public Executor MessageExecutor() {`
 `ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();`
 `executor.setCorePoolSize(corePoolSize);`
 `executor.setMaxPoolSize(maxPoolSize);`
 `executor.setQueueCapacity(queueCapacity);`
 `executor.setKeepAliveSeconds(keepAliveSeconds);`
 `executor.setThreadNamePrefix(threadNamePrefix);`
 `executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());`
 `executor.initialize();`
 `return executor;`
 `}`
`}`
`Copy code`

Spring asynchronous core method:Spring code skills that you can't put down

The processing is different according to the return value, which can be divided into the following situations:Spring code skills that you can't put down

I heard that the cache is easy to use. I didn’t expect it to be so easy to use

Spring cache architecture diagram:

Spring code skills that you can't put down

It currently supports multiple caches:

Spring code skills that you can't put down

Here we take caffeine as an example, which is officially recommended by spring.

The first step is to introduce the related jar package of caffeine

`<dependency>`
 `<groupId>org.springframework.boot</groupId>`
 `<artifactId>spring-boot-starter-cache</artifactId>`
`</dependency>`
`<dependency>`
 `<groupId>com.github.ben-manes.caffeine</groupId>`
 `<artifactId>caffeine</artifactId>`
 `<version>2.6.0</version>`
`</dependency>`
`Copy code`

Step 2: ConfigureCacheManager, onEnableCaching

`@Configuration`
`@EnableCaching`
`public class CacheConfig {`
 `@Bean`
 `public CacheManager cacheManager(){`
 `CaffeineCacheManager cacheManager = new CaffeineCacheManager();`
 `//Caffeine configuration`
 `Caffeine<Object, Object> caffeine = Caffeine.newBuilder()`
 `//Expires after a fixed time after the last write`
 `.expireAfterWrite(10, TimeUnit.SECONDS)`
 `//Maximum number of cache entries`
 `.maximumSize(1000);`
 `cacheManager.setCaffeine(caffeine);`
 `return cacheManager;`
 `}`
`}`
`Copy code`

Step 3: useCacheableAnnotation get data

`@Service`
`public class CategoryService {`
 
 `//Category is the cache name, #type is the specific key, and El expressions are supported`
 `@Cacheable(value = "category", key = "#type")`
 `public CategoryModel getCategory(Integer type) {`
 `return getCategoryByType(type);`
 `}`
 `private CategoryModel getCategoryByType(Integer type) {`
 `System.out.println ("obtain different classification data according to different types:" + type + ")`
 `CategoryModel categoryModel = new CategoryModel();`
 `categoryModel.setId(1L);`
 `categoryModel.setParentId(0L);`
 `Categorymodel.setname ("appliance")`
 `categoryModel.setLevel(3);`
 `return categoryModel;`
 `}`
`}`
`Copy code`

When calling the categoryservice. Getcategory() method, start fromcaffineGet data from the cache. If data can be obtained, the data will be returned directly without entering the method body. If you can’t get the data, you can get the data directly from the code in the method body and put it into the cafe cache.

One last word (please pay attention, don’t whore me in vain)

If this article is helpful or enlightening to you, please pay attention to it. Your support is the biggest driving force for me to insist on writing.

Seek one key three links: like, forward and watch.
Spring code skills that you can't put down
From: juejin.cn/post/6931630572720619534