探讨spring Bean 指定初始化顺序的若干姿势和装配条件

在某次开发中涉及需要控制bean的加载顺序,但是网上搜索出来的结果,在实际执行的时候并不能生效,所以记录一下相关的内容,预防后续继续踩坑

@Order注解的作用

控制的是bean的执行的顺序,而不是加载顺序,大致用法如下:

1、切面定义执行顺序

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

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

2、过滤器定义执行顺序

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

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

3、事件监听器定义执行顺序

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

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

4、注入集合类型bean的顺序

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

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

@Autowired
private List myBeans; // 自动按照@Order排序

指定bean加载的初始化顺序

1、构造方法依赖

需要注意循环依赖的问题

2、DependsOn注解使用

控制 bean 的实例化顺序,但是 bean 的初始化操作(如构造 bean 实例之后,调用@PostConstruct注解的初始化方法)顺序则不能保证

@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、BeanDefinitionRegistryPostProcessor和ConfigurationClassPostProcessor知识

非典型用法,通过自定义BeanDefinitionRegistryPostProcessor调整执行顺序。

  • 利用 BeanDefinitionRegistryPostProcessor 在所有 bean 定义(BeanDefinition)信息将要被加载,bean 实例还未创建的时候加载,优先于 BeanFactoryPostProcessor 执行的机制,可以给 spring 容器中再额外添加一些 bean。
  • ConfigurationClassPostProcessor,是 spring boot 提供的扫描你的注解并解析成 BeanDefinition 的重要组件。
// 第一步:spring.factories 里面注册一个 ApplicationContextInitializer:
org.springframework.context.ApplicationContextInitializer=com.demo.bootstrap.MyApplicationContextInitializer

// 之所以要注册 ApplicationContextInitializer 其实一个是为了接下来注册一个 BeanDefinitionRegistryPostProcessor 到 spring 中,这是是因为没有找到直接使用 spring.factories SPI 来注册的方式,只好用 ApplicationContextInitializer 来间接注册
public class MyApplicationContextInitializer implements ApplicationContextInitializer {

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    // 注意如果是在 spring cloud 的上下文中,不要作这个事情
    applicationContext.addBeanFactoryPostProcessor(new MyBeanDefinitionRegistryPostProcessor());
}

}

// 第二步:实现 BeanDefinitionRegistryPostProcessor 的主要逻辑,注册目标 bean:
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    // 在 ConfigurationClassPostProcessor 前手动注册一个 bean
    registry.registerBeanDefinition("systemConfigService", new RootBeanDefinition(SystemConfigService.class));
}

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

@Override
public int getOrder() {
    // 只要排在 ConfigurationClassPostProcessor 之前即可
    return Ordered.LOWEST_PRECEDENCE - 1;
}

}

##额外说明
1、能不能用 @Component 或者其他的注解的方式注册 BeanDefinitionRegistryPostProcessor ?答案是不能。
@Component 注解的方式注册能注册上的前提是能被 ConfigurationClassPostProcessor 扫描到,也就是说用 @Component 注解的方式来注册,
注册出来的 bean 一定不可能排在 ConfigurationClassPostProcessor 前面,而我们的目的就是在所有的 bean 扫描前注册你需要的 bean,
这样才能排在其他所有 bean 前面,所以是不能用注解注册 BeanDefinitionRegistryPostProcessor 的。

2、需要注意如果其他 BeanFactoryPostProcessor、BeanPostProcessor 在创建或者执行逻辑时触发了 bean 注入,
也会导致被注入的 bean 早于 preInstantiateSingletons 阶段提前初始化。

4、BeanPostProcessor 扩展

最后再介绍另外一种非典型的使用方式,如非必要,请不要用这种方式来控制 bean 的加载顺序

先创建两个测试 bean
@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 {
    // 在bean实例化之前做某些操作
    if ("HDemo1".equals(beanName)) {
        HDemo2 demo2 = beanFactory.getBean(HDemo2.class);
    }
    returnnull;
}

}

决定是否创建装配Bean

1、@Conditional

这是一个基础注解,它可以与任何自定义的 Condition 接口实现一起使用。

// Condition 接口定义了一个 matches 方法,如果该方法返回 true,则对应的 Bean 会被注册。
@Bean
@Conditional(MyCondition.class)
public MyBean myBean() {
    return new MyBean();
}

public static class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 定义条件逻辑
return true; // 或 false
}
}

2、@ConditionalOnProperty

当指定的配置属性存在,且满足给定的值时,相关的 Bean 才会被注册

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

3、@ConditionalOnClass / @ConditionalOnMissingClass

分别在类路径上有或没有指定类时,对应的 Bean 才会被注册。

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

4、@ConditionalOnBean / @ConditionalOnMissingBean

分别在上下文中存在或不存在指定 Bean 时,将注册新的 Bean

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

5、@ConditionalOnExpression

根据 SpEL (Spring Expression Language) 表达式的值来决定是否注册 Bean

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

部分参考内容: Spring Boot 如何让你的 bean 在其他 bean 之前完成加载


这是一个从 https://juejin.cn/post/7368690450899042304 下的原始话题分离的讨论话题