【走近Spring】控制Spring IoC容器对Bean(含@Configuration配置类)的加载顺序(@DependsOn注解的使用)
为什么要控制Bean的加载顺序?
@Order注解等并不能控制Bean的加载顺序的~~因为Spring在解析Bean的时候,根本就没有参考这个注解。另外@Configuration配置类的加载,也不会受到@Order注解的影响,它拿到配置的数组后,仅仅就是一个for循环遍历去解析。
另外需要说明的一点是:@Configuration注解的解析顺序,在Spring Boot环境下会受到影响的(毕竟Boot都是自动的,而不是我们手动传值的) 相关注解有:@AutoConfigureAfter、@AutoConfigureBefore、@AutoconfigureOrder等等
Spring容器载入bean顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。
但Spring能保证如果A依赖B(如beanA中有@Autowired B的变量),那么B将先于A被加载(这属于Spring容器内部就自动识别处理了)。但如果beanA不直接依赖B,我们如何让B仍先加载?
需要的场景如下:
- bean A 间接(并不是直接@Autowired)依赖 bean B。如bean A有一个属性,需要在初始化的时候对其进行赋值(需要在初始化的时候做,是因为这个属性其实是包装了其它的几个Bean的,比如说代理了Bean B),所以这就形成了Bean A间接的依赖Bean B了
- bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。
以上是两种典型的,Bean初始化的时候存在依赖关系的情况,都可以通过@DependsOn来解决。(当然有的时候可以通过别的方式间接解决,比如特殊接口SmartInitializingSingleton ,又或者是Spring Boot提供的CommandLineRunner、ApplicationRunner等接口,但这些都不是本文研究的重点)。
Bean加载顺序、依赖关系示例
这里面解答的方案,不考虑上面说到的使用SmartInitializingSingleton等间接的方案
准备工作:(两个controller和一个service)
@Controller
public class HelloController {
public HelloController() {
System.out.println("HelloController 初始化。。。");
}
@ResponseBody
@GetMapping("/hello")
public String helloGet() throws Exception {
return "hello...Get";
}
}
@Controller
public class AsyncHelloController {
public AsyncHelloController() {
System.out.println("AsyncHelloController 初始化。。。");
}
}
@Service
public class HelloServiceImpl implements HelloService {
public HelloServiceImpl() {
System.out.println("HelloServiceImpl 初始化。。。");
}
}
启动容器,打印顺序(初始化顺序如下:)
HelloServiceImpl 初始化。。。
AsyncHelloController 初始化。。。
HelloController 初始化。。。
需要注意的是:这个demo的日志都是放在默认的构造函数里面的,因此即使你使用了@Autowired,也是不会打乱构造函数的执行顺序的,因为@Autowired的解析发生在给属性赋值的populate()方法里(具体查之前博文或者源码),这个时候自己已经实例化了,才会去给属性赋值。所以如果你要求的时机稍微比较晚可以在赋值期间、或者实例化期间去。
@DependsOn:让HelloController在AsyncHelloController之前实例化
//@DependsOn // 这里面写String数组。不写不会生效,但是若写了,名字要写正确,否则会报错的
@DependsOn({"helloController"}) // 名称必须写对,必须是容器里存在的Bean,否则启动报错的(fast-fail是好事)
@Controller
public class AsyncHelloController {
...
}
HelloServiceImpl 初始化。。。
HelloController 初始化。。。 --> HelloController先被实例化了~~~
AsyncHelloController 初始化。。。
需要特别注意的是,使用@DependsOn注解时,一定要注意父子容器的问题(因为它底层也是getBean())。比如下面这样在service层依赖controller的话,就报错:
@DependsOn({"helloController"}) //NoSuchBeanDefinitionException: No bean named 'helloController' available
@Service
public class HelloServiceImpl implements HelloService {
...
}
SpringBoot环境下,不会报错。
使用@Lazy间接实现
@Lazy
public class AsyncHelloController {
...
}
HelloServiceImpl 初始化。。。
HelloController 初始化。。。
我们发现它只有两句输出,这个时候AsyncHelloController还没有实例化。只有首次访问它的时候才会实例化,所以我们是通过间接的方式实现了这个效果。
这种方式不建议使用在这种DependsOn的场景,因为它不是为了这个而生的。若有别的Bean @Autowired了它之类的,这种做法显然就失效了。
//May be used on any class directly or indirectly annotated with @Component
// or on methods annotated with @Bean
// 若这个Bean xml里也配置了,就会议xml里配置的为准
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
String[] value() default {};
}
@DependsOn 用于@Bean注解上的使用
@Configuration配置类顺序控制
@Configuration配置类也是容器里面一个特殊的Bean,因为它不需要完成业务功能。
纯Spring环境
由于在纯Spring环境下,Config配置类都是由我们手动指定传进去的,所以Spring并没有再对它进行排序处理。如下非web环境和web环境:
public static void main(String[] args) {
// 这里Config是自己指定、所以加载顺序就是我们传入的顺序
new AnnotationConfigApplicationContext(RootConfig.class, Root2Config.class);
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class, Root2Config.class};
}
@Configuration的加载顺序,并不影响@Bean的互相引用:
@Configuration
public class RootConfig {
// 虽然入参里的Parent 在配置类Root2Config里,但spring还是能够去容器中找过来的。
@Bean
public Child child(Parent parent) {
System.out.println(parent);
return new Child();
}
}
@Configuration
public class Root2Config {
@Bean
public Parent parent() {
return new Parent();
}
}
配置文件加载顺序为:RootConfig 、 Root2Config
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class, Root2Config.class};
}
我们可以看到,Config的先后顺序,并不影响@Bean的引用。
此处需要特别说明的一点是:请不要循环引用,否则会报错~(这个和Bean的属性赋值方面的循环引用还是不一样的,有点类似构造器的循环引用。我们知道的是,Spring是不能解决构造器的循环引用的)
参考: 【小家Spring】控制Spring IoC容器对Bean(含@Configuration配置类)的加载顺序(@DependsOn注解的使用) |