想写好一个 Starter , 控制配置的加载和Bean的加载是其中至关重要的一步.
这一篇把如何做好Bean管理做了一个总结 , 来好好看看Bean如何控制顺序.
Bean 名称控制
同一个包里面
Bean 名称根据字母优先级排序 ,是可以控制Bean的加载流程不同包里面
按照包名层级有序加载不同依赖包
和包的加载顺序有关注解控制
对Bean加载顺序起作用的注解主要有 : @DependsOn , @Bean
@Bean
主要还是和方法的加载有关 , 由上到下自然加载@DependsOn
用于控制互相依赖 , 先加载依赖的包@SpringBootApplication
中 scanBasePackages
可以控制包有序特别注意
@Resouce 和 @Autowired
并不能控制依赖关系构造器中
@Autowired 可以影响到加载顺序我们可以知道这里有两个阶段 :
这里面其实涉及到几个点 :
S1 : ClassPathBeanDefinitionScanner 中 doScan 时是按照 @SpringBootApplication 配置的扫描路径进行扫描
S2 : DefaultListableBeanFactory # registerBeanDefinition 时是放入有序的 list
总结 : 这里可以看到 , 从package 扫描到后续加载都是 list 控制的有序 , 所以 @Bean 的加载是有序的 , 并且我们可以通过 scanBasePackages 注解控制相对有序!!!
这里其实很多文章里面说的是 @Bean 并不是写在前面的会先加载 ,但是在我个人读取源码的过程中 , 发现@Bean的书写顺序确实是能控制顺序的 , 从源码的角度来分析一下 :
S1 : Spring 发现该@Bean是一个配置类 , 后续触发ConfigurationClassBeanDefinitionReader对配置类进行读取
S2 : doProcessConfigurationClass 中 retrieveBeanMethodMetadata 获取到所有的 Set
S3 : 循环所有的 Set , 将 BeanName 放入 List 中
整个过程中最重要的就是 S2
, 虽然它是个 Set ,但是看看源码就知道 , 这是一个 LinkedHashSet , 所以它其实是有序的
private Set retrieveBeanMethodMetadata(SourceClass sourceClass) {
AnnotationMetadata original = sourceClass.getMetadata();
// 通过 @Bean 获取到所有的 MethodMetadata , 这里的 Set 实际上是 LinkdedHashSet
Set beanMethods = original.getAnnotatedMethods(Bean.class.getName());
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
// .............. 省略一些其他场景
}
}
return beanMethods;
}
实际上在我更换 @Configuration 中 @Bean 的书写顺序时 , 得到的效果也和源码中看的一致.
DependsOn 的处理应该算是在 Spring Bean 加载流程的第二阶段处理的.
在这个阶段中SpringIOC获取Bean的BeanDefinition , 并且对Bean类型进行分析同时判断其关联关系
.
这个流程主要在 AbstractBeanFactory
中实现.
C- AbstractBeanFactory # doGetBean
// 这里获取的就是@DependsOn 注解中的方法名
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
// S1 : 这里需要判断这个依赖是否已经注册, 会从 S2 里面的Map 中获取
if (isDependent(beanName, dep)) {
throw new BeanCreationException(.....);
}
// S2 : 这里把DependsOn 的Bean注册到Map中 ,避免重复
registerDependentBean(dep, beanName);
// S3 : 递归获取 , 这里就是按照Bean全流程递归处理
getBean(dep);
}
}
在以往很多场景中 ,我都是通过@Resource 引入某个对象 , 来实现优先加载 . 但是事实证明这种方式是不生效的.
这里面涉及到Spring循环依赖的相关特点 ,即 Spring 依赖时创建的对象 , 还没有被初始化了.
但是如果在构造器中引入依赖 , 是可以影响到Bean的加载顺序的 :
@Bean
public BeanThree getBeanThree(BeanTwo beanTwo) {
return new BeanThree(beanTwo);
}
这是简单说一下原理 , 在构造器载入的场景下 , Spring 需要先将依赖的对象初始化后 , 再作为 argsToUse 传入被构建的对象中
:
// argsToUse 即为构造器中的对象
bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
流程跟踪 :
// S1 : Spring 容器初始化 BeanThree
C- AbstractAutowireCapableBeanFactory # doCreateBean
- instanceWrapper = createBeanInstance("getBeanThree", mbd, args);
// S2 : 通过构造器进行初始化
C- AbstractAutowireCapableBeanFactory # instantiateUsingFactoryMethod
- return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
// S3 : 初始化 BeanThree 前先初始化 BeanTwo
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
// 3-1 省略获取工厂的相关逻辑
// 3-2 获取到构造器方法
- public .....ConfigutationA.getBeanThree(.....BeanTwo)
Method factoryMethodToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
//... 省略
if (factoryMethodToUse == null || argsToUse == null) {
List candidates = null;
if (mbd.isFactoryMethodUnique) {
if (factoryMethodToUse != null) {
// 此处获取所有的构造器方法
candidates = Collections.singletonList(factoryMethodToUse);
}
}
//... 省略额外的构造器处理
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
LinkedList causes = null;
for (Method candidate : candidates) {
// 3-3 此处就开始处理构造器中的所有参数 , 以及需要autowired处理的参数
// - 在这个环节中 , 会优先调用依赖的对象的初始化操作
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
}
}
// S3-4 继续调用 BeanThree 初始化逻辑
bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));
return bw;
}
// S4 : 初始化 BeanTwo
// S5 : 继续初始化 BeanThree
总结 : 可以看到 , 如果构造器中存在了需要初始化后才能注入的对象 ,是会触发提前加载
. 但是如果单纯的类中的属性 , 实际上是在后面 populationBean 中进行的处理
Bean 控制有序的方式还是有很多种的 , 以上的加载方式只能说以Idea启动时能确定是存在影响的 , 但是会不会由于某种场景例如JVM的加载等等破坏这种操作 , 暂时是不清楚的.
同时是否会由于一些代理等操作影响到这种加载 , 也存在不确定.
不过如果遇到需要控制Bean加载的场景 , 不妨试试上面的办法 , 说不定能满足需求.
下一篇来看一下配置文件如何进行有序的控制.