Spring - the most commonly used extension points

1 Introduction

When we talk about spring, the first thing that may come to mind is IOC (Inversion of Control) and AOP (Aspect-Oriented Programming).

That's right, they are the cornerstone of spring, and thanks to their excellent design, spring can stand out from many excellent frameworks.

In addition, in the process of using spring, have we found that its expansion ability is very strong. Due to the existence of this advantage, spring has a strong tolerance, so that many third-party applications can easily fall into the embrace of spring. For example: rocketmq, mybatis, redis, etc.

2. Custom interceptor

Compared with the root spring interceptor of spring mvc interceptor, it can obtain web object instances such as HttpServletRequest and HttpServletResponse.

The top-level interface of the spring mvc interceptor is: HandlerInterceptor, which contains three methods:

  • preHandle executes before the target method is executed

  • postHandle Executed after the target method is executed

  • afterCompletion is executed when the request is completed

For convenience, we generally use the HandlerInterceptorAdapter class that implements the HandlerInterceptor interface.

This interceptor can be used if there are scenarios for authorization authentication, logs, and statistics.

The first step is to inherit the HandlerInterceptorAdapter class to 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("===权限校验===");
        return true;
    }
}

The second step is to register the interceptor to the spring container:

@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {
 
    @Bean
    public AuthInterceptor getAuthInterceptor() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor());
    }
}

In the third step, spring mvc can automatically intercept the interface and verify the authority through the interceptor when requesting the interface.

3. Get the Spring container object

In our daily development, we often need to get beans from the Spring container, but do you know how to get the Spring container object?

3.1, 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");
    }
}

Implement the BeanFactoryAware interface, and then rewrite the setBeanFactory method to get the spring container object from this method.

3.2, 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");
    }
}

Implement the ApplicationContextAware interface, and then rewrite the setApplicationContext method, and you can also get the spring container object from this method.

3.3, 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");
    }
}

4. Global exception handling

In the past, when we were developing the interface, if an exception occurred, in order to give the user a more friendly prompt, for example:

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/add")
    public String add() {
        int a = 10 / 0;
        return "成功";
    }
}

If you do not do any processing to request the add interface result, an error will be reported directly:

what? Can the user see the error message directly?

This kind of interaction 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 = "成功";
    try {
        int a = 10 / 0;
    } catch (Exception e) {
        result = "数据异常";
    }
    return result;
}

After the interface is modified, when an exception occurs, it will prompt: "data exception", which is more user-friendly.

It looks pretty good, but there is a problem. . .

It's okay if it's just one interface, but if there are hundreds or thousands of interfaces in the project, do you have to add exception catching code?

The answer is no, then global exception handling comes in handy: RestControllerAdvice.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "数据异常";
        }
        if (e instanceof Exception) {
            return "服务器内部异常";
        }
        retur nnull;
    }
}

You only need to handle the exception in the handleException method, and you can use it safely in the business interface, and you no longer need to catch the exception (someone handles it uniformly). Really cool.

5. Type converter

Spring currently supports 3 type converters:

  • Converter<S,T>: Convert an object of type S to an object of type T

  • ConverterFactory<S, R>: Convert S-type objects to R-type and subclass objects

  • GenericConverter: It supports the conversion of multiple source and target types, and also provides the context of source and target types. This context allows you to perform type conversion based on annotations or information on attributes.

These three types of converters are used in different scenarios. Let's take Converter<S,T> as an example. If: In the entity object receiving parameters in the interface, there is a field whose type is Date, but the actual parameter passed is a string type: 2021-01-03 10:20:15, how to deal with it?

The first step is to define an entity User:

@Data
public class User {

    private Long id;
    private String name;
    private Date registerDate;
}

The second step is to 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;
    }
}

The third step is to 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());
    }
}

The fourth step is to call the interface

@RequestMapping("/user")
@RestController
public class UserController {

    @RequestMapping("/save")
    public String save(@RequestBody User user) {
        return "success";
    }
}

When requesting the interface, the registerDate field in the User object will be automatically converted to the Date type.

6. Import configuration

Sometimes we need to introduce other classes in a certain configuration class, and the imported classes are also added to the spring container. At this time, you can use the @Import annotation to complete this function.

If you look at its source code, you will find that the imported classes support three different types.

But I think it is best to explain the configuration classes of ordinary classes and @Configuration annotations separately, so four different types are listed:

6.1, common class

This introduction method is the simplest, and the imported class will be instantiated as a bean object.

public class A {
}

@Import(A.class)
@Configuration
public class TestConfiguration {
}

Introduce class A through the @Import annotation, and spring can automatically instantiate the A object, and then inject it through the @Autowired annotation where it needs to be used:

@Autowired
private A a;

Is it surprising? Bean can be instantiated without @Bean annotation.

6.2. Configuration class

This introduction method is the most complicated, because the @Configuration annotation also supports a variety of combined annotations, such as:

  • @Import

  • @ImportResource

  • @PropertySource etc.

public class A {
}

public class B {
}

@Import(B.class)
@Configuration
public class AConfiguration {

    @Bean
    public A a() {
        return new A();
    }
}

@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}

The configuration class annotated with @Configuration is introduced through the @Import annotation, and the classes introduced with annotations such as @Import, @ImportResource, and @PropertySource related to the configuration class will be recursively introduced all at once.

6.3、ImportSelector

This import method needs to implement the ImportSelector interface:

public class AImportSelector implements ImportSelector {

private static final String CLASS_NAME = "com.sue.cache.service.test13.A";
    
 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{CLASS_NAME};
    }
}

@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}

The advantage of this method is that the selectImports method returns an array, which means that multiple classes can be imported at the same time, which is very convenient.

6.4、ImportBeanDefinitionRegistrar

This import method needs to implement the ImportBeanDefinitionRegistrar interface:

public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
        registry.registerBeanDefinition("a", rootBeanDefinition);
    }
}

@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
}

This method is the most flexible. The BeanDefinitionRegistry container registration object can be obtained in the registerBeanDefinitions method, and the creation and registration of the BeanDefinition can be manually controlled.

7. When the project starts

Sometimes we need to customize some additional functions when the project starts, such as: loading some system parameters, completing initialization, warming up the local cache, etc. What should we do?

The good news is that springboot provides:

  • CommandLineRunner

  • ApplicationRunner

These two interfaces help us achieve the above requirements.

Their usage is quite simple. Take the ApplicationRunner interface as an example:

@Component
public class TestRunner implements ApplicationRunner {

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
        loadDataService.load();
    }
}

Implement the ApplicationRunner interface, rewrite the run method, and implement your own customization requirements in this method.

If there are multiple classes in the project that implement the ApplicationRunner interface, how to specify their execution order?

The answer is to use the @Order(n) annotation, the smaller the value of n, the earlier it will be executed. Of course, the order can also be specified through the @Priority annotation.

8. Modify BeanDefinition

Before instantiating the Bean object, Spring IOC needs to read the relevant properties of the Bean, save it in the BeanDefinition object, and then instantiate the Bean object through the BeanDefinition object.

What if you want to modify the properties in the BeanDefinition object?

Answer: We can implement the BeanFactoryPostProcessor interface.

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("id", 123);
        beanDefinitionBuilder.addPropertyValue("name", "苏三说技术");
        defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
    }
}

In the postProcessBeanFactory method, you can get the related object of BeanDefinition and modify the properties of the object.

9. Before and after initializing the Bean

Sometimes, you want to implement some logic of your own before and after the bean is initialized.

At this time, it can be realized: BeanPostProcessor interface.

The interface currently has two methods:

  • postProcessBeforeInitialization should be called before the initialization method.

  • postProcessAfterInitialization This method is called after the initialization method.

For example:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User) {
            ((User) bean).setUserName("苏三说技术");
        }
        return bean;
    }
}

If there is a User object in spring, set its userName to: Su San said technology.

In fact, the annotations we often use, such as: @Autowired, @Value, @Resource, @PostConstruct, etc., are implemented through AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor.

10. Initialization method

Currently, there are many ways to initialize beans in spring:

  1. Use the @PostConstruct annotation

  2. Implement the InitializingBean interface

10.1. Use @PostConstruct annotation

@Service
public class AService {
    @PostConstruct
    public void init() {
        System.out.println("===初始化===");
    }
}

Add the @PostConstruct annotation to the method that needs to be initialized, so that it has the ability to initialize.

10.2. Implement the InitializingBean interface

@Service
public class BService implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("===初始化===");
    }
}

Implement the InitializingBean interface and rewrite the afterPropertiesSet method, which can complete the initialization function.

11. Before closing the container

Sometimes, we need to do some extra work before closing the spring container, such as: closing resource files, etc.

At this time, you can implement the DisposableBean interface and rewrite its destroy method:

@Service
public class DService implements InitializingBean, DisposableBean {
 
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy");
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet");
    }
}

In this way, before the spring container is destroyed, the destroy method will be called to do some extra work.

Usually, we will implement the InitializingBean and DisposableBean interfaces at the same time, and rewrite the initialization method and destruction method.

12. Custom scope

We all know that there are only two Scopes supported by spring by default:

  • singleton singleton, each bean obtained from the spring container is the same object.

  • There are multiple instances of prototype, and the beans obtained from the spring container are different objects each time.

spring web has extended Scope again, adding:

  • RequestScope The beans obtained from the spring container in the same request are all the same object.

  • SessionScope The beans obtained from the spring container by the same session are all the same object.

Even so, some scenes just didn't meet our requirements.

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

This requires a custom Scope.

The first step is to 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;
    }
}

The second step is to 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());
    }
}

The third step uses the newly defined Scope:

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

Guess you like

Origin blog.csdn.net/qq_34272760/article/details/127496505