SpringBoot Bean Specify Initialization Order Details

Recently, we encountered the problem that SpringBoot out-of-container class initialization relies on beans inside the container. Since there is a certain order in which beans inside the container are initialized, we looked up the data on the Internet and recorded it here.

0. Preface

This article describes several possible ways to control the loading order between bean s

  • @Order indicates order
  • @AutoConfigureOrder
  • Construction Method Dependency
  • @DependOn comment
  • BeanPostProcessor Extension

1. @Order and @AutoConfigureOrder instructions

1.1. Wrong Posture

Below we will describe two typical misused positions for notes, one for @Order and one for @AutoConfigureOrder

1.1.1 @Order

err.case1: Add Order comment on class

Conclusion: Sprintboot default automatic scan, the size of order value, has nothing to do with the initialization order between specified bean s.

A common misconception is that by adding this Order comment to a class, you can specify the initialization order between bean s. The smaller the order value, the higher the priority. Let's actually test whether this is the case

Let's create two DemoBean s and specify a different Order order

@Order(4)
@Component
publicclass BaseDemo1 {
    private String name = "base demo 1";

    public BaseDemo1() {
        System.out.println(name);
    }
}

@Order(3)
@Component
publicclass BaseDemo2 {
    private String name = "base demo 2";

    public BaseDemo2() {
        System.out.println(name);
    }
}

From the previous point of view, a low order value has a higher priority, so BaseDemo2 should be initialized first, tested in practice, and the output is as follows

err.case2: Add @Order to Bean declaration method in configuration class

Conclusion: With @bean annotation scan, the size of the order value is independent of the initialization order between the specified beans.

In addition to the automatic scan above, Beans can also be scanned by the @bean annotation. Here's a demonstration of the wrong case that specifies the order in which beans are loaded in the configuration class

Also let's create two new test bean s

publicclass BaseDemo3 {
    private String name = "base demo 3";

    public BaseDemo3() {
        System.out.println(name);
    }
}

publicclass BaseDemo4 {
    private String name = "base demo 4";

    public BaseDemo4() {
        System.out.println(name);
    }
}

Next, define the bean s in the configuration class

@Configuration
publicclass ErrorDemoAutoConf {
    @Order(2)
    @Bean
    public BaseDemo3 baseDemo3() {
        returnnew BaseDemo3();
    }

    @Order(1)
    @Bean
    public BaseDemo4 baseDemo4() {
        returnnew BaseDemo4();
    }
}

Similarly, if the @Order annotation is valid, BaseDemo4 should be initialized first

From the actual test output above, it can be seen that the @Order comment does not work in the above way either. If interested students can try it, by reversing the order of the two methods in the configuration class above, BaseDemo4 will be loaded first

err.case3: @Order Annotation Modifier Configuration Class

Conclusion: The @Order annotation is used to specify the loading order of the configuration class, and the initialization order is independent of the @Order size value.

This is also a common error case, considering that the @Order comment is used to specify the loading order of configuration classes, but is this really the case?

We create a configuration class for two tests

@Order(1)
@Configuration
publicclass AConf {
    public AConf() {
        System.out.println("AConf init!");
    }
}

@Order(0)
@Configuration
publicclass BConf {
    public BConf() {
        System.out.println("BConf init");
    }
}

If the @Order comment is valid, then the BConf configuration class will initialize first, so let's test it

As you can see from the above results, BConf is not loaded first; Of course, this usage posture, in fact, is no different from the first error case, the configuration class is also a bean, it does not work before, and it certainly does not work here.

 

So is it because we don't understand it correctly? In fact, after this @Order is placed on the configuration class, is the Bean defined in this configuration class taking precedence over the Bean defined in another configuration class?

In the same case we tested, we defined three bean s and two conf s

publicclass Demo1 {
    private String name = "conf demo bean 1";

    public Demo1() {
        System.out.println(name);
    }
}

publicclass Demo2 {
    private String name = "conf demo bean 2";

    public Demo2() {
        System.out.println(name);
    }
}

publicclass Demo3 {
    private String name = "conf demo bean 3";

    public Demo3() {
        System.out.println(name);
    }
}

Then we put Demo1, Demo3 in one configuration, Demo2 in another

@Order(2)
@Configuration
publicclass AConf1 {
    @Bean
    public Demo1 demo1() {
        returnnew Demo1();
    }

    @Bean
    public Demo3 demo3() {
        returnnew Demo3();
    }
}

@Order(1)
@Configuration
publicclass BConf1 {

    @Bean
    public Demo2 demo2() {
        returnnew Demo2();
    }
}

If the @Order comment actually controls the order in which beans in the configuration class are loaded, then beans in BConf1 should be loaded first, that is, Demo2 will take precedence over Demo1, Demo3. To actually test, output such as

The output above is not what we expected, so the @Order comment is also wrong to determine the order of the configuration classes

1.1.2. @AutoConfigureOrder

From the naming point of view, this annotation is used to specify the order of configuration classes, but there are many errors with this annotation, most of which are due to the lack of a real understanding of its usage scenarios

Next let's demonstrate the wrong use of case s

Create two new configuration classes within the project, using annotations directly

@Configuration
@AutoConfigureOrder(1)
publicclass AConf2 {
    public AConf2() {
        System.out.println("A Conf2 init!");
    }
}

@Configuration
@AutoConfigureOrder(-1)
publicclass BConf2 {
    public BConf2() {
        System.out.println("B conf2 init!");
    }
}

BConf loads priority when annotations take effect

From the output, it is different from what we expected. Does this comment then affect the order of beans in the configuration class, not the configuration class itself?

Similarly, let's design a case to verify

publicclass DemoA {
    private String name = "conf demo bean A";

    public DemoA() {
        System.out.println(name);
    }
}

publicclass DemoB {
    private String name = "conf demo bean B";

    public DemoB() {
        System.out.println(name);
    }
}

publicclass DemoC {
    private String name = "conf demo bean C";

    public DemoC() {
        System.out.println(name);
    }
}

Corresponding Configuration Class

@Configuration
@AutoConfigureOrder(1)
publicclass AConf3 {
    @Bean
    public DemoA demoA() {
        returnnew DemoA();
    }

    @Bean
    public DemoC demoC() {
        returnnew DemoC();
    }
}

@Configuration
@AutoConfigureOrder(-1)
publicclass BConf3 {

    @Bean
    public DemoB demoB() {
        returnnew DemoB();
    }
}

If DemoB is loaded later, the above view is incorrect, and the results are as follows

So the question is, @AutoConfigureOrder doesn't specify the order of the configuration classes. It's also called this name. What? The quintessence is misleading!!!

Next let's see how @Order and @AutoConfigureOrder are used correctly

1.2. Instructions

1.2.1. @Order

Take a look at the official comment for this note first

{@code @Order} defines the sort order for an annotated component. Since Spring 4.0, annotation-based ordering is supported for many kinds of components in Spring, even for collection injection where the order values of the target components are taken into account (either from their target class or from their {@code @Bean} method). While such order values may influence priorities at injection points, please be aware that they do not influence singleton startup order which is an orthogonal concern determined by dependency relationships and {@code @DependsOn} declarations (influencing a runtime-determined dependency graph).

The Order comment is initially used to specify the priority of the facets; It has been enhanced since 4.0 by specifying the order of bean s in a collection when @Order supports its injection.

It has no effect on the order between bean s of a single instance. This sentence can also be validated according to the tests we have done above.

Next we need to look at the scenario where the order is specified when injecting a collection through the @Order annotation.

First, we define two beans that implement the same interface and add the @Order annotation.

publicinterface IBean {
}

@Order(2)
@Component
publicclass AnoBean1 implements IBean {

    private String name = "ano order bean 1";

    public AnoBean1() {
        System.out.println(name);
    }
}

@Order(1)
@Component
publicclass AnoBean2 implements IBean {

    private String name = "ano order bean 2";

    public AnoBean2() {
        System.out.println(name);
    }
}

Then, in a test bean, inject the list of IBean s, and we need to test whether the beans in this list are in the same order as the @Order rule we defined

@Component
publicclass AnoTestBean {

    public AnoTestBean(List<IBean> anoBeanList) {
        for (IBean bean : anoBeanList) {
            System.out.println("in ano testBean: " + bean.getClass().getName());
        }
    }
}

As expected, anoBean2 should be in the front of the anoBeanList collection

From the output above, you can also see that the order in the list is the same as what we expected, and that AnoOrderBean1 has nothing to do with the loading order and annotations of AnoOrderBean2

1.2.2. @AutoConfigureOrder

This note is used to specify the loading order of the configuration file, but the previous tests did not work. What is the correct posture to use?

@AutoConfigureOrder applies to the order of AutoConfigs in externally dependent packages and cannot be used to specify the order within this package

To verify the above, we create two new projects again and specify the order in which the classes are automatically configured

Project 1 is configured as follows:

@AutoConfigureOrder(1)
@Configuration
@ComponentScan(value = {"com.git.hui.boot.order.addition"})
publicclass AdditionOrderConf {
    public AdditionOrderConf() {
        System.out.println("additionOrderConf init!!!");
    }
}

Note that auto-configuration classes need to be loaded correctly in the project's /META-INF/spring. Definition in factories file

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.order.addition.AdditionOrderConf

Project 2 is configured as follows:

@Configuration
@AutoConfigureOrder(-1)
@ComponentScan("com.git.hui.boot.order.addition2")
publicclass AdditionOrderConf2 {

    public AdditionOrderConf2() {
        System.out.println("additionOrderConf2 init!!!");
    }
}

Then we add a configuration inside the project

@AutoConfigureOrder(10)
@Configuration
publicclass OrderConf {
    public OrderConf() {
        System.out.println("inner order conf init!!!");
    }
}

Because the annotations apply to the order of the auto-configuration classes in the external dependency packages, AdditionOrderConf2 precedes AdditionOrderConf1 if correct;

OrderConf will not be affected by annotations. By default, internally defined configuration classes will outperform external dependencies, and the output below will confirm our statement (of course, to verify this, you should also adjust the order of the two external project configuration classes and see if the loading order changes, which we omitted here).

 

1.3. Summary

This article mainly introduces the common misuse of @Order and @AutoConfigureOrder on the Internet, and gives the correct use case s.

Here are a few simple sentences about the correct posture

  • The @Order annotation cannot specify the order in which beans are loaded, it applies to the priority of the AOP and the order in which beans are in the collection when multiple beans are injected into the collection.

  • @AutoConfigureOrder specifies the order in which externally dependent AutoConfigs are loaded (that is, the configuration bean s priority defined in the/META-INF/spring.factories file), and it is not useful to use this annotation in the current project.

  • The same @AutoConfigureBefore and @AutoConfigureAfter notes apply to the same extent as @AutoConfigureOrder.

 

2. Initialization order specification

2.1. Construction Method Dependency

This is arguably the simplest and most common position to use, but when using it, you need to be aware of issues such as cyclical dependence.

We know that one of the ways beans are injected is through the construction method, which allows us to resolve the initialization order between beans with priority requirements

For example, if we create two beans that require CDemo2 to be initialized before CDemo1, how can we do that?

@Component
public class CDemo1 {

    private String name = "cdemo 1";

    public CDemo1(CDemo2 cDemo2) {
        System.out.println(name);
    }
}

@Component
public class CDemo2 {

    private String name = "cdemo 1";

    public CDemo2() {
        System.out.println(name);
    }
}

The measured output is as follows and is in line with our expectations

While this approach is intuitive and simple, it has several limitations

  • An injection relationship is required, such as CDemo2 being constructed into CDemo1. If you need to specify a priority between two beans that do not have an injection relationship, this is not appropriate (for example, I want a bean to execute before all other beans are initialized)
  • Circular dependency problems, such as the CDemo1 parameter in the construction method of Demo2 above, cause a circular dependency and the application cannot start

Another point to note is that there should be no complex, time-consuming logic in the construction method that can slow down the application's startup time

2.2. @DependOn annotation

This is a dependency problem specifically designed to solve beans, which can be used when one bean needs to be initialized after another bean has been initialized

It's also easier to use. Here's a simple example case

@DependsOn("rightDemo2")
@Component
public class RightDemo1 {
    private String name = "right demo 1";

    public RightDemo1() {
        System.out.println(name);
    }
}

@Component
public class RightDemo2 {
    private String name = "right demo 2";

    public RightDemo2() {
        System.out.println(name);
    }
}

The above note is emancipated on RightDemo1, indicating that the initialization of RightDemo1 depends on the rightDemo2 bean

When using this annotation, it is important to note that it controls the order in which beans are instantiated, but the order in which beans are initialized (such as calling the initialization method of the @PostConstruct annotation after constructing a bean instance) cannot be guaranteed, as illustrated by one of our examples below

@DependsOn("rightDemo2")
@Component
public class RightDemo1 {
    private String name = "right demo 1";

    @Autowired
    private RightDemo2 rightDemo2;

    public RightDemo1() {
        System.out.println(name);
    }

    @PostConstruct
    public void init() {
        System.out.println(name + " _init");
    }
}

@Component
public class RightDemo2 {
    private String name = "right demo 2";

    @Autowired
    private RightDemo1 rightDemo1;

    public RightDemo2() {
        System.out.println(name);
    }

    @PostConstruct
    public void init() {
        System.out.println(name + " _init");
    }
}

Note that the code above, although circularly dependent, was injected through the @Autowired annotation, so it won't cause the application to fail to start. Let's take a look at the output first

The interesting thing is that we use the @DependsOn annotation to make sure that RightDemo1 is created before RightDemo2 is created.

So as you can see from the output of the construction method, first instance RightDemo2, then instance RightDemo1;

Then you can see from the output of the initialization method that in the above scenario, although the RightDemo2 bean was created, its initialization code is executed later

Off-topic topic:
Interested students can try to test the code above Dependency Injection Delete for @Autowired, where two bean s have no mutual injection dependency and when executed, the output order is different

2.3. BeanPostProcessor

Finally, an atypical way of using beans, if not necessary, should not be used to control the order in which beans are loaded

Create two test bean s first

@Component
public class HDemo1 {
    private String name = "h demo 1";

    public HDemo1() {
        System.out.println(name);
    }
}

@Component
public class HDemo2 {
    private String name = "h demo 2";

    public HDemo2() {
        System.out.println(name);
    }
}

We want HDemo2 to be loaded before HDemo1, and with BeanPostProcessor we can do this as follows

@Component
public class DemoBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
    private ConfigurableListableBeanFactory beanFactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    @Nullable
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        // Do something before the bean is instantiated
        if ("HDemo1".equals(beanName)) {
            HDemo2 demo2 = beanFactory.getBean(HDemo2.class);
        }
        return null;
    }
}

Focus on postProcessBeforeInstantiation, which is called before a bean is instantiated, giving us the opportunity to control the order in which beans are loaded

Isn't it a little foolish to see this kind of cocktail operation, like I have a bean that I want to load before other beans are instantiated after the application starts, is this possible?

Here is a simple example demo that overrides the postProcessAfterInstantiation method of DemoBeanPostProcessor and loads our FDemo bean after the application is created

@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    if ("application".equals(beanName)) {
        beanFactory.getBean(FDemo.class);
    }

    return true;
}


@DependsOn("HDemo")
@Component
public class FDemo {
    private String name = "F demo";

    public FDemo() {
        System.out.println(name);
    }
}

@Component
public class HDemo {
    private String name = "H demo";

    public HDemo() {
        System.out.println(name);
    }
}

As you can see from the output below, the order of instantiation of HDemo and FDemo comes first

2.4. Summary

Before summarizing, let's point out that a complete bean creation distinguishes two pieces of order in this article

  • Instantiate (call construction method)
  • Initialization (inject dependency properties, call @PostConstruct method)

This paper mainly describes three ways to control the loading order of bean s, which are

  • Control the initialization order between dependent bean s by constructing method dependencies, but note the issue of circular dependency
  • The @DependsOn annotation controls the order of instances between beans, and it is important to note that the order in which beans are initialized cannot be guaranteed
  • BeanPostProcessor mode to manually control the loading order of bean s

 

3 Reference:

 

Tags: Spring Boot

Posted by PurpleMonkey on Sun, 08 May 2022 19:09:55 +0300