[Microservice] Detailed explanation of the use of spring control bean loading order

Table of contents

I. Introduction

2. Use the @order annotation to control the order

2.1 Examples of @order annotation usage

2.2 Order annotation sequence invalidation problem

2.2.1 Solutions to @order invalidation problem

2.3 Implement the Ordered interface

3. Use @dependon annotation to control the sequence

4. AutoConfiguration annotations control bean loading order

4.1 @AutoConfigureBefore operation demonstration

4.2 @AutoConfigureOrder operation demonstration

4.3 Source code interpretation and analysis

5. Customize ApplicationContextInitializer

5.1 Introduction to ApplicationContextInitializer

5.2 Using ApplicationContextInitializer

5.3 ApplicationContextInitializer controls the loading sequence

6. Usage scenarios

6.1 Resolve bean dependencies

6.2 Set the highest priority for certain configuration classes

6.3 Dependency transfer

7. Write at the end of the article


I. Introduction

During development using the spring framework, you may encounter the following situations:

  • A bean is dependent on another bean, that is, the creation of bean-b must depend on bean-a;
  • A certain bean is dependent on many other beans. For example, after bean-a is initialized, other beans need to rely on bean-a to initialize their own business;
  • ...

There are many scenarios like this. To sum up, this involves the loading order of beans. How to solve it? Several common solutions are listed below.

2. Use the @order annotation to control the order

The @order annotation is an annotation under the spring-core package. The function of @Order is to define the priority of the execution order of beans in the Spring IOC container (the order here can also be understood as the order in which they are stored in the container). Configuration dependencies sometimes appear during the development process. For example, @ConditionalOnBean(B.class) is used to inject object A, which means that A must be injected when an instance of B.class must exist in the container. At this time, we must ensure that the B object is injected before the A object is injected.

2.1 Examples of @order annotation usage

There are two classes as follows, Demo1 and Demo2. Add @Order annotation to the classes respectively.

@Component
@Order(1)
public class Demo1 {
    @Bean
    public UserService serviceA(){
        System.out.println("serviceA 执行");
        return new UserService();
    }
}

The two classes each create a UserService bean.

@Component
@Order(2)
public class Demo2 {
    @Bean
    public UserService serviceB(){
        System.out.println("serviceB 执行");
        return new UserService();
    }
}

Run the following code and observe the test effect

public class OrderTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanScanConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames){
            System.out.println(beanName);
        }
    }
}

It can be seen that the bean of the demo1 class with a smaller order number is created first than the bean of demo2;

2.2 Order annotation sequence invalidation problem

By adding @order to the class above, you can control the order of beans created by the class. Is this really the case? If at this time, we increase the order number of demo1 and decrease the order number of demo2, the effect you will see is that serviceA is still output first. This is the order annotation order invalid problem. Regarding this issue, spring official documents also have explanations. The translated meaning is as follows:

You can declare the @Order annotation at the target class level and on the @Bean method, possibly for a single bean definition (if multiple definitions use the same bean class). @Order values ​​may affect the priority of injection points, but note that they do not affect singleton startup order, which is an orthogonal concern determined by dependencies and @DependsOn declarations.

2.2.1 Solutions to @order invalidation problem

Solution 1

Place the two classes that need to control the creation order of beans in different package paths.

Run the test code again. When the order number of demo2 is smaller, you can see the expected effect of the beans created by demo2 being output first.

Solution 2

Rename the class, for example, change the class of demo2 to ADemo, so that the class ADemo is placed before Demo2 in the same package path.

After this modification, run the test code again to observe the effect. At this time, ADemo will give priority to Demo1.

2.3 Implement the Ordered interface

Using the ordered interface provided by spring, you only need to implement this interface with a custom class and rewrite the getOrder method inside. In the return value of the getOrder method, the smaller the value, the higher the priority. The following two customized ones are managed by sppring. Class that implements the Ordered interface

@Component
public class Listener1 implements Ordered {

    @Bean
    public UserService userService1(){
        System.out.println("userService1 bean");
        return new UserService();
    }

    @Override
    public int getOrder() {
        return 100;
    }
}
@Component
public class Listener2 implements Ordered {

    @Bean
    public UserService userService2(){
        System.out.println("userService2 bean");
        return new UserService();
    }

    @Override
    public int getOrder() {
        return 10;
    }
}

Similar to the ordered interface, there is PriorityOrdered, with similar usage. Interested students can dig further.

3. Use @dependon annotation to control the sequence

When the creation or loading of a certain bean-a needs to explicitly depend on another bean-b, you can consider using the dependon annotation. In other words, bean-b should be created before bean-a;

There are two classes as follows, OrderDemo1 and OrderDemo2

@Component
@DependsOn("orderDemo2")
public class OrderDemo1 {

    public OrderDemo1(){
        System.out.println("OrderDemo1 ...");
    }

}

The creation of OrderDemo1 depends on OrderDemo2

@Component
public class OrderDemo2 {

    public OrderDemo2(){
        System.out.println("OrderDemo2 ...");
    }

}

Run the program and you can see that OrderDemo2 is created before OrderDemo1

Since this method controls the order through the name of the bean (string), if you change the class name of the bean, you will probably forget to change all the annotations that use it, which will be a big problem, so this method Generally not recommended, but it is still a way to control the order of bean creation.

4. AutoConfiguration annotations control bean loading order

Spring Boot will dynamically determine the configuration order of automatic configuration classes based on the situation in the current container. It provides us with three annotations: @AutoConfigureBefore, @AutoConfigureAfter, and @AutoConfigureOrder:

@AutoConfigureBefore

Used on an automatic configuration class, indicating that the automatic configuration class needs to be configured and loaded after the other specified automatic configuration class is configured.

@AutoConfigureAfter

Used on an automatic configuration class, indicating that the automatic configuration class needs to be configured and loaded before the other specified automatic configuration class is configured.

@AutoConfigureOrder

Determine the priority order of configuration loading , indicating absolute order (the smaller the number, the higher the priority)

4.1 @AutoConfigureBefore operation demonstration

Define two configuration classes DbConfig1 and DbConfig2, the code is as follows

@Configuration
public class DbConfig1 {

    public DbConfig1(){
        System.out.println("DbConfig1 构建方法...");
    }

}

Among them, the annotation AutoConfigureBefore is used on the DbConfig2 class, and it is expected that DbConfig2 will be loaded before DbConfig1.

@Configuration
@AutoConfigureBefore(DbConfig1.class)
public class DbConfig2 {

    public DbConfig2(){
        System.out.println("DbConfig2 构建方法...");
    }

}

It’s not done yet. You still need to configure DbConfig1 and DbConfig2 into the spring.factories file in the resources directory for automatic assembly.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.congge.config.DbConfig2,\
com.congge.config.DbConfig1

Run the above code and observe the console output. You can see that DbConfig2 is loaded before DbConfig1.

We can also change the annotation to AutoConfigureAfter to see what the effect is.

@Configuration
@AutoConfigureAfter(DbConfig1.class)
public class DbConfig2 {

    public DbConfig2(){
        System.out.println("DbConfig2 构建方法...");
    }

}

Run the code again and you can see that this time DbConfig1 is loaded first.

4.2 @AutoConfigureOrder operation demonstration

You can also use the AutoConfigureOrder annotation to control the loading order of different configuration classes. The smaller the number, the higher the priority. Add two configuration classes and use this annotation to mark them respectively.

@Configuration
@AutoConfigureOrder(2)
public class OrderConfig1 {

    public OrderConfig1(){
        System.out.println("OrderConfig1 加载...");
    }
}

The value in OrderConfig2 is larger. In theory, OrderConfig1 is loaded first.

@Configuration
@AutoConfigureOrder(7)
public class OrderConfig2 {

    public OrderConfig2(){
        System.out.println("OrderConfig2 加载...");
    }
}

Similarly, using AutoConfigureOrder to control the loading order of configuration classes also requires configuring it to spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.congge.config.DbConfig2,\
com.congge.config.DbConfig1,\
com.congge.config.OrderConfig1,\
com.congge.config.OrderConfig2

Run the above code and see the following effect, indicating that OrderConfig1 is loaded better than OrderConfig2

4.3 Source code interpretation and analysis

The most critical code is in the AutoConfigurationImportSelector class. During the bean loading process, spring loads the automatic configuration classes from spring.factories and sorts them according to conditions. The last line of code in the selectImports() method is sorted, as follows

Following the sortAutoConfigurations method, the final sorting logic comes to the getInPriorityOrder method below;

In this method, sorting is done in three ways:

  • Sort alphabetically first;
  • Then sort according to @AutoConfigureOrder;
  • Finally, sort according to @AutoConfigureBefore and @AutoConfigureAfter;

It is not difficult to find from the order of configuration that the final decision lies with the two annotations @AutoConfigureAfter and @AutoConfigureBefore.

5. Customize ApplicationContextInitializer

Students who are familiar with the life cycle and loading process of spring beans must know that during the container startup process, beans are generated by parsing the configuration in the xml file or by scanning the path and parsing the beans. These beans will eventually be stored in a container. It is managed by spring. In this case, developers can manually intervene in the parsing process of this bean. Through the relevant extension points provided by spring, changing the position of the bean in the container can control the order of the beans. Let's look at the specific operation process.

5.1 Introduction to ApplicationContextInitializer

Let’s first look at the introduction on spring’s official website:

The roughly translated meaning is as follows

  • Callback interface used to initialize Spring ConfigurableApplicationContext before the spring container is refreshed. (Shortly speaking, the initialize method of this class is called before the container is refreshed. And an instance of the ConfigurableApplicationContext class is passed to this method);
  • Typically used in web applications that require programmatic initialization of the application context. For example, registering attribute sources or activating configuration files according to the context environment;
  • Sortable (implement the Ordered interface, or add the @Order annotation);

5.2 Using ApplicationContextInitializer

Create a new class MyAppInitializer and implement the ApplicationContextInitializer interface

public class MyAppInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("-----MyAppInitializer initialize-----");
    }
}

Then add it in the startup class

@SpringBootApplication
public class BootApp {
    
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(BootApp.class);
        application.addInitializers(new MyAppInitializer());
        application.run(args);
    }
}

Run the main program above and see the console output the following information when starting

From the above introduction, we know that implementing the ApplicationContextInitializer interface will be loaded before the container is refreshed, which corresponds to the spring source code, that is, the refreshContext(context) method. Students who understand the loading process of spring beans should know that the refreshContext is called The main function of the refresh method of AbstractApplicationContext is to register the beans in the spring container, process the beans, and broadcast functions. In short, in this method, a series of creation and storage of beans in the system are completed. process.

Based on this, according to the above effect, after implementing the ApplicationContextInitializer interface, the loading timing of its classes will be earlier. Therefore, you can use this feature to artificially intervene in the creation order of the bean before the bean is actually created. Register the specified class into the spring context.

5.3 ApplicationContextInitializer controls the loading sequence

Customize a class to implement the ApplicationContextInitializer interface

public class MyAppInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("-----MyAppInitializer initialize-----");
        applicationContext.addBeanFactoryPostProcessor(new MyBeanFactoryPostProcessor());
    }
}

Customize a class to implement the interface BeanDefinitionRegistryPostProcessor, override the postProcessBeanDefinitionRegistry method, and manually register the classes that need to control the order loaded into the container. Note that after following the above process, do not use relevant annotations for the configuration classes you want to control. For example, what we want to control below is that the order of the OrderService class is at the front.

public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition.setBeanClass(OrderService.class);
        beanDefinitionRegistry.registerBeanDefinition("orderService",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

Configure MyAppInitializer into the spring.factories file

org.springframework.context.ApplicationContextInitializer=\
com.congge.config.MyAppInitializer

Run the main program and observe the console output as follows, which shows that the OrderService is not only managed by the container, but also the order is at the front, which achieves the purpose of controlling the order of beans.

6. Usage scenarios

At this point, let’s look at the question raised at the beginning of the article. Why do we need to control the loading order of beans in spring? Regarding this issue, here are the following common scenarios.

6.1 Resolve bean dependencies

For example, during initialization, configuration class B needs to use certain attributes in configuration class A, and configuration class C needs to use certain attributes in B. In this case of cascading dependencies, in order to avoid dependencies during the startup process If the problem causes startup failure, it can be solved by controlling the order of configuring beans.

6.2 Set the highest priority for certain configuration classes

For example, we have a configuration class for product management, which needs to write the data from the database to the redis cache when the project is started, and refresh the product data regularly, and this class also provides a static method for external access. In this scenario, since the static method is provided externally, other classes can directly call its method. If it is not loaded first, when the product data is requested, the product has not been loaded, and then problems will occur. Therefore, you need to ensure that this configuration bean is loaded first.

6.3 Dependency transfer

When the program needs to verify and intercept interface parameters at different gradients, a common approach is to use AOP to reduce interference to the main business process. In this case, if after the AOP logic processing of class A is completed, Continue to pass it to the AOP of the next level B for processing, which requires controlling the execution order of different AOP classes. At this time, it is necessary to control the execution order of different AOPs.

7. Write at the end of the article

In some special business scenarios, reasonably controlling the loading order of beans can help us solve many complex business needs. It can also be used as a functional extension point provided by spring, which plays an important role in the spring system. This article That’s the end of this article, thanks for watching.

Guess you like

Origin blog.csdn.net/zhangcongyi420/article/details/133249021