最近在给公司搭建一套脚手架,其中有对一些通用功能,比如shiro授权模块的代码,以及网关解析token的代码做一些封装,发现对于spring的bean关系管理,存在一些值得记录的问题
场景 1.A类代码中,依赖于B类中注入的一个bean
如果搭建过项目的工具包,封装过一些功能的同仁肯定会遇见这个问题,举个小列子:
@Configuration
public class A{
@Bean
public B b() {
return new B();
}
}
@Configuration
public Class C{
@Autowired
private B b;
@Bean D d() {
return new D();
}
}
很明显,C是依赖于A中注入的Bean【B】,那么启动项目时,我们如果无法保证A在C之前先初始化出bean的话,那么启动肯定会报C类中找不到存在的Bean【B】
因此,需要一些手段来管理Bean的这些加载、初始化的顺序!
解决方案
这里面分两种环境:
1.脚手架包
@AutoConfigureBefore
@AutoConfigureAfter
@AutoConfigureOrder
这里我们需要注意一个点就是,必须是含有spring.facotries
文件,且当前模块不含有启动类,才可以使用,否则,是不会生效的,spring
的加载SPI
机制会根据我们的指示去加载。
@AutoConfigureOrder只能改变外部依赖的@Configuration的顺序。
如何理解是外部依赖呢?
能被你工程内部scan到的包,都是内部的Configuration,而spring引入外部的Configuration,都是通过spring特有的spi文件:spring.factories
总结:@AutoConfigureOrder能改变spring.factories中的@Configuration的顺序
2. 含有启动类的包
@Depeonds
依赖加载
这就是直接写在启动类的一个包下的代码,我们可以使用@DepeondsOn("b")
来保证启动时候,spring加载bean顺序,按照我们预期的方向去走。
@DependsOn
注解可以用来控制bean的创建顺序,该注解用于声明当前bean依赖于另外一个bean。所依赖的bean会被容器确保在当前bean实例化之前被实例化
3. 简单方案【一般场景】
@Bean
参数注入
示例:
@Bean
public D d(B b){
return new D();
}
Spring
在初始化@Bean标记的注解时,如果发现容器没有参数传入的bean
,会自动的去初始化,但是这种不适用于跨类的注入,如果B
是常规的bean还好,但是如果B的初始化也依赖很多其他的bean
,这种方法使用起来,会比较麻烦一点
4.ObjectProvider
@Autowired
private ObjectProvider<UserDetailsService> objectProvider;
有兴趣的同学可以了解下,这个是后面我发现的一个好方法,某些场景下也比较好用,启动包的时候不会去检查依赖关系,只会在调用的时候,去尝试获取bean
5.SpringUtils.getBean(T.class)
某些情况下,我们并不是在启动项目的时候就需要立刻加载bean
,而是在它启动之后,被调用时才加载,但是启动项目的时候,spring
又会去检测依赖的bean
,这个时候,我们可以不采用@Autowired
去引入bean
,而是在它被调用的时候,直接利用根据去获取,打个时间差,也可以避免这种问题!
使用误区
1. @Order
注解来控制
可能最开始想到的就是这个注解,但是很遗憾,这个是无法控制bean
的加载顺序的
通过@Order
这个标注进行顺序的控制标注加在普通的方法上或者类上一点鸟用都没有
目前用的比较多的有以下3点:
- 控制
AOP
的类的加载顺序,也就是被@Aspect
标注的类 - 控制
ApplicationListener
实现类的加载顺序 - 控制
CommandLineRunner
实现类的加载顺序
2.@Condition
簇注解
@ConditionalOnBean``@ConditionalOnClass
… 来控制顺序
但是源代码中,对于这个簇的使用,有这么一句话:
The condition can only match the bean definitions that have been processed by the
application context so far and, as such, it is strongly recommended to use this
condition on auto-configuration classes only. If a candidate bean may be created by
another auto-configuration, make sure that the one using this condition runs after.
百度翻译:
条件只能匹配已由处理的bean定义!到目前为止的应用程序上下文,因此,强烈建议使用此条件仅适用于自动配置类。如果候选bean可以由另一个自动配置,请确保使用此条件的配置在之后运行。
这也就是说,使用这一类注解时,Spring
是根据当时加载bean
的实际情况来判断是否加载bean
的,这个也没有办法保证bean
的加载顺序,且推荐使用方式为:适用于自动配置类!
总结:在确切知道bean
的加载有先后顺序的情况下,使用@ConditionalOnBean
类型的注解,才会有效!