Do you know the 11 most commonly used extension points in Spring?

Preface

When we talk about spring, the first thing that may come to mind is IOC(inversion of control) and AOP(aspect-oriented programming).

Yes, they are the cornerstone of spring. Thanks to their excellent design, spring can stand out from many excellent frameworks.

In addition, have we discovered it during the use of spring 扩展能力非常强? Due to the existence of this advantage, spring has strong tolerance capabilities, allowing many third-party applications to easily fall into the embrace of spring. For example: rocketmq, mybatis, redis, etc.

Today I will talk to you about the 11 most commonly used extension points in Spring.

1. Custom interceptor

Compared with the spring interceptor, the spring mvc interceptor can obtain HttpServletRequestand HttpServletResponsewait for web object instances.

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

  • preHandle is executed before the target method is executed
  • postHandle is executed after the target method is executed
  • afterCompletion is executed when the request is completed

For convenience, we generally use HandlerInterceptorthe implementation class of the interface HandlerInterceptorAdapter.

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

The first step is to inherit HandlerInterceptorAdapterthe 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("===权限校验===");
        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, when requesting the interface, spring mvc can automatically intercept the interface through the interceptor and verify the permissions.

2. Get the Spring container object

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

2.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 BeanFactoryAwarethe interface and then override setBeanFactorythe method to get the spring container object from the method.

2.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 ApplicationContextAwarethe interface and then override setApplicationContextthe method, and you can also get the spring container object from the method.

2.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");
    }
}
复制代码

3. Global exception handling

In the past, when we were developing interfaces, if an exception occurred, 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 "成功";
    }
}
复制代码

If the add interface is requested without any processing, an error will be reported directly:

what? Can users see error messages directly?

This interaction method gives the user 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 modification, when an exception occurs, it will prompt: "Data exception", which is more user-friendly.

It looks pretty good, but there's a problem. . .

It's fine if there's just one interface, but if there are hundreds or thousands of interfaces in the project, do we need to add exception catching code?

The answer is no, and this is where 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 handleExceptionto handle exceptions in the method, and you can use it in the business interface with confidence. There is no need to catch exceptions (someone has handled them uniformly). It's so cool.

4.Type converter

Spring currently supports 3 type converters:

  • Converter<S,T>: Convert S type object to T type object
  • 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 context for source and target types. This context allows you to perform type conversion based on annotations or information on attributes.

The usage scenarios of these three type converters are different. Let's take Converter<S,T>为an example. Suppose: In the entity object that receives 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 Converterthe 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";
    }
}
复制代码

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

5. Import configuration

Sometimes we need to introduce other classes into a configuration class, and the introduced classes are also added to the spring container. At this time, you can use @Importannotations to complete this function.

If you look at its source code, you will find that the introduced class supports three different types.

But I think it’s best to explain the ordinary classes and the configuration classes annotated with @Configuration separately, so four different types are listed:

5.1 Ordinary category

This introduction method is the simplest, and the imported class will instantiate the bean object.

public class A {
}

@Import(A.class)
@Configuration
public class TestConfiguration {
}
复制代码

By @Importintroducing class A through annotations, spring can automatically instantiate the A object, and then @Autowiredinject it through annotations where it needs to be used:

@Autowired
private A a;
复制代码

Isn’t it surprising? @BeanBeans can be instantiated without annotations.

5.2 Configuration class

This introduction method is the most complex, because @Configurationannotations also support multiple combinations of 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 introduced by the @Configuration annotation through the @Import annotation will recursively introduce the classes related to the configuration class @Import, @ImportResource, and other annotations, and introduce them all at once.@PropertySource

5.3 ImportSelector

This introduction method requires the implementation ImportSelectorof the 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 selectImportsthe method returns an array, which means that multiple classes can be introduced at the same time, which is very convenient.

5.4 ImportBeanDefinitionRegistrar

This introduction method requires the implementation ImportBeanDefinitionRegistrarof the 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. You can registerBeanDefinitionsobtain BeanDefinitionRegistrythe container registration object in the method and manually control the creation and registration of BeanDefinition.

6. When the project starts

Sometimes we need to customize some additional functions when the project starts, such as loading some system parameters, completing initialization, preheating 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 ApplicationRunnerinterfaces as an example:

@Component
public class TestRunner implements ApplicationRunner {

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
        loadDataService.load();
    }
}
复制代码

Implement ApplicationRunnerthe interface, rewrite runthe method, and implement your own customized needs in the 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 @Order(n)annotations. The smaller the value of n, the earlier it is executed. Of course, you can also @Priorityspecify the order through annotations.

7. Modify BeanDefinition

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

What should I do if I want to modify the properties in the BeanDefinition object?

Answer: We can implement BeanFactoryPostProcessorinterfaces.

@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 obtain the BeanDefinition related object and modify the properties of the object.

8. Before and after initializing Bean

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

This can be achieved at this time: BeanPostProcessorinterface.

This 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 Sanshuo Technology.

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

9.Initialization method

Currently, the most commonly used methods to initialize beans in spring are:

  1. Use @PostConstruct annotation
  2. Implement the InitializingBean interface

9.1 Using @PostConstruct annotation

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

Add annotations to methods that need to be initialized @PostConstructso that they have the ability to be initialized.

9.2 Implement the InitializingBean interface

@Service
public class BService implements InitializingBean {

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

Implement InitializingBeanthe interface and override afterPropertiesSetthe method. The initialization function can be completed in this method.

10. Before closing the container

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

At this time, you can implement DisposableBeanthe interface and override its destroymethods:

@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 additional work.

Normally, we will implement both the InitializingBean and DisposableBean interfaces and override the initialization method and destruction method.

11. Custom scope

We all know that spring supports Scopeonly two types by default:

  • singleton singleton, the bean obtained from the spring container is the same object every time.
  • Prototype has multiple instances, and the beans obtained from the spring container are different objects each time.

Spring web has expanded Scope and added:

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

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

For example, what should we do if we want to obtain the same object from the spring container in the same thread?

This requires customizing 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 is to use the newly defined Scope:

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

Guess you like

Origin blog.csdn.net/2301_76607156/article/details/130526430