Discussing Various Methods and Assembly Conditions for Specifying spring Bean Initialization Order

During a development project, I needed to control the loading order of beans, but the solutions found online did not work in actual execution, so I am recording this content to avoid stepping into the same pit in the future.

Role of the @Order Annotation

It controls the execution order of beans, not the loading order. The general usage is as follows:

1. Define execution order for aspects

@Aspect
@Order(1)
public class MyAspectOne {
    // ...
}

@Aspect
@Order(2)
public class MyAspectTwo {
// …
}

2. Define execution order for filters

@Order(1)
@WebFilter(urlPatterns = "/*")
public class MyFilterOne implements Filter {
    // Filter implementation
}

@Order(2)
@WebFilter(urlPatterns = “/*”)
public class MyFilterTwo implements Filter {
// Filter implementation
}

3. Define execution order for event listeners

@Component
@Order(1)
public class MyEventListenerOne implements ApplicationListener {
    // ...
}

@Component
@Order(2)
public class MyEventListenerTwo implements ApplicationListener {
// …
}

4. Order of injected collection-type beans

@Bean
@Order(1)
public MyInterface myBeanOne() {
    return new MyBeanOne();
}

@Bean
@Order(2)
public MyInterface myBeanTwo() {
return new MyBeanTwo();
}

@Autowired
private List myBeans; // Automatically sorted according to @Order

Specify the initialization order of bean loading

1. Constructor dependency

Pay attention to the circular dependency issue

2. Usage of the @DependsOn Annotation

It controls the instantiation order of beans, but cannot guarantee the order of bean initialization operations (such as calling the @PostConstruct annotated initialization method after constructing the bean instance).

@Configuration
public class DynamicConfigtion {
@Bean
public FilePropertiesSource filePropertiesSource(ConfigurableEnvironment environment) {
    FilePropertiesSource filePropertiesSource = new FilePropertiesSource();
    environment.getPropertySources().addLast(filePropertiesSource);
    return filePropertiesSource;
}

}

@DependsOn(“filePropertiesSource”)
@Controller
public class PathVariableController implements ApplicationEventPublisherAware {

@Resource
private UserMapper userMapper;

@Resource
private DynamicConfig dynamicConfig;

@Value("${name}")
private String name;

@Autowired
private Environment environment;

}

3. Knowledge of BeanDefinitionRegistryPostProcessor and ConfigurationClassPostProcessor

This is an atypical usage: adjust the execution order by customizing BeanDefinitionRegistryPostProcessor.

  • By leveraging the mechanism of BeanDefinitionRegistryPostProcessor, which loads when all bean definition (BeanDefinition) information is about to be loaded and no bean instances have been created yet, and executes before BeanFactoryPostProcessor, you can add additional beans to the Spring container.
  • ConfigurationClassPostProcessor is an important component provided by Spring Boot that scans your annotations and parses them into BeanDefinition.
// Step 1: Register an ApplicationContextInitializer in spring.factories:
org.springframework.context.ApplicationContextInitializer=com.demo.bootstrap.MyApplicationContextInitializer

// The reason for registering ApplicationContextInitializer is that we need to subsequently register a BeanDefinitionRegistryPostProcessor into Spring. Since no direct registration method via spring.factories SPI was found, we have to use ApplicationContextInitializer for indirect registration
public class MyApplicationContextInitializer implements ApplicationContextInitializer {

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    // Note: Do not do this if you are in a Spring Cloud context
    applicationContext.addBeanFactoryPostProcessor(new MyBeanDefinitionRegistryPostProcessor());
}

}

// Step 2: Implement the main logic of BeanDefinitionRegistryPostProcessor and register the target bean:
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    // Manually register a bean before ConfigurationClassPostProcessor
    registry.registerBeanDefinition("systemConfigService", new RootBeanDefinition(SystemConfigService.class));
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}

@Override
public int getOrder() {
    // It only needs to be ordered before ConfigurationClassPostProcessor
    return Ordered.LOWEST_PRECEDENCE - 1;
}

}

Additional Notes

  1. Can you register BeanDefinitionRegistryPostProcessor using @Component or other annotation-based methods? The answer is no.
    The premise that registration via @Component can work is that it can be scanned by ConfigurationClassPostProcessor. That means when registering via @Component,
    the resulting bean can never be placed before ConfigurationClassPostProcessor. But our goal is to register the required bean before all other beans are scanned,
    so that it can be loaded before all other beans. Therefore, you cannot register BeanDefinitionRegistryPostProcessor with annotations.

  2. Note that if other BeanFactoryPostProcessor or BeanPostProcessor trigger bean injection during their creation or execution,
    it will also cause the injected bean to be initialized earlier than the preInstantiateSingletons phase.

4. BeanPostProcessor extension

Finally, let's introduce another atypical usage. Unless it is absolutely necessary, do not use this method to control bean loading order.

// Create two test beans first
@Component
publicclass HDemo1 {
    private String name = "h demo 1";
public HDemo1() {
    System.out.println(name);
}

}

@Component
publicclass HDemo2 {
private String name = “h demo 2”;

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

}

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

@Override
@Nullable
public Object postProcessBeforeInstantiation(Class<!--?--> beanClass, String beanName) throws BeansException {
    // Perform certain operations before bean instantiation
    if ("HDemo1".equals(beanName)) {
        HDemo2 demo2 = beanFactory.getBean(HDemo2.class);
    }
    returnnull;
}

}

Decide whether to create and assemble a Bean

1. @Conditional

This is a basic annotation that can be used with any custom implementation of the Condition interface.

// The Condition interface defines a matches method. If this method returns true, the corresponding Bean will be registered.
@Bean
@Conditional(MyCondition.class)
public MyBean myBean() {
    return new MyBean();
}

public static class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Define conditional logic
return true; // or false
}
}

2. @ConditionalOnProperty

The relevant Bean will only be registered when the specified configuration property exists and matches the given value.

@Bean
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public MyBean myBean() {
    return new MyBean();
}

3. @ConditionalOnClass / @ConditionalOnMissingClass

The corresponding Bean will only be registered when the specified class is present or absent on the classpath, respectively.

@Bean
@ConditionalOnClass(name = "com.example.SomeClass")
public MyBean myBean() {
    return new MyBean();
}

4. @ConditionalOnBean / @ConditionalOnMissingBean

A new Bean will be registered when the specified Bean exists or does not exist in the context, respectively.

@Bean
@ConditionalOnBean(OtherBean.class)
public MyBean myBean() {
    return new MyBean();
}

5. @ConditionalOnExpression

Decide whether to register the Bean based on the value of the SpEL (Spring Expression Language) expression.

@Bean
@ConditionalOnExpression("${feature.enabled} == true")
public MyBean myBean() {
    return new MyBean();
}

Part of the content referenced from: How Spring Boot makes your bean complete loading before other beans


This is a discussion topic separated from the original thread at https://juejin.cn/post/7368690450899042304