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:
- Wrong Bean Loading Order for SpringBoot Series Tutorials Using Posture to Dismiss Ballads
- https://segmentfault.com/a/1190000020873720
- Project: https://github.com/liuyueyi/spring-boot-demo
- Project: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/008-beanorder