Spring之什么是配置类
什么是配置类
一、将自定义配置类注册到容器中
首先从三行代码开始说起
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AppConfig.class);
applicationContext.refresh();
对应的AppConfig如下所示:
@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig {}
在org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中来完成配置类的解析
方法说明如下所示:
从方法中可以看到构建并校验配置模型基于容器中的配置类
然后拿到此时容器中的BeanDefinition的Name,挨个判断哪些是配置类
1.1、根据BeanDefinition的attributes属性判断是否解析成过配置类
判断BeanDefinition的attributes属性是为了判断当前配置类是否已经解析过,根据日志的打印
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
表示已经将当前的BeanDefinition解析成了配置类,而BeanDefinition的attributes属性为Null的时候,表示还没有来进行解析过,对于没有解析过的BeanDefinition,需要来解析判断是否是一个配置类
1.2、判断BeanDefinition类型
因为在这个阶段的上一个阶段是for循环,所以这里是对每个BeanDefinition来做判断,判断当前BeanDefinition是否是配置类。
断点展示一下,当前容器中的所有BeanDefinition哪些是配置类
断点发现,只有自己注册进去的AppConfig类才是AnnotatedGenericBeanDefinition且类名和源文件中的类名一致,所以走第一个解析过程:
1.3、根据源文件信息判断是否存在Configuration注解
利用metadata来进行判断
如果类上标注有Configuration注解且注解中的属性proxyBeanMethods为true,那么在对应的BD属性attributes中会添加是FULL配置类;
但是如果类上没有Configuration注解且注解中的属性proxyBeanMethods为false,表示一个Lite配置类
或者根本就没有Configuration注解,而是存在着以下情况中的一种,也是一个Lite配置类
①如果类是一个接口的话,肯定不是配置类;
②如果类上存在Component、ComponentScan、Import、ImportResource中的一个注解,那么是一个配置类
③如果类上都没有,但是类中方法存在@Bean方法,那么说明是一个配置类
1.3.1、流程图说明BD如何判断是配置类小结
用一个流程图进行表示
1.3.2、例子
LITE配置类:
public class AService {
@Bean
public BService bService(){
return new BService();
}
}
LITE配置类:
@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig {}
LITE配置类:
@Configuration(proxyBeanMethods = false)
public class BService {}
LITE配置类:
@Import(AService.class)
public class BService {}
LITE配置类:
@Component
public class BService {}
LITE配置类:
@ImportResource(value = {"classpath:spring.xml"})
public class BService {}
FULL配置类:
@Configuration(proxyBeanMethods = true)
public class BService {}
二、解析配置类
2.1、配置类解析结果分类
既然已经判断出来了配置类,那么就要来进行解析。那么解析出来的结果是什么?
两种结果:①解析出来BeanDefinition;②还是配置类
因为最终的目标是BeanDefinition,所以如果还是配置类的话,还需要继续进行解析,所以可能存在递归
2.2、配置类解析成BeanDefinition源码
解析出来的配置类对应的BD会被放置到configCandidates集合中保存起来,方便在下面来进行操作。
具体解析源码
2.2.1、构建解析器解析
首先构建一个配置类的解析器,然后对保存起来的配置类对应的BD来进行解析;然后将已经将BD解析成配置类的BD保存起来,方便后续判断;
2.2.2、遍历循环所有候选配置类对应的BD
遍历循环所有候选配置类对应的BD,调用配置类的解析器来进行解析
对应当前第一轮循环中的BD是AppConfig对应的BD肯定是AnnotatedBeanDefinition类型的,所以直接看这个解析方法
2.2.2.1、创建配置类盛放当前BD,然后解析
一定要注意这里的do...while循环
2.2.2.2、解析@Component
解析类上标注有@Component的类
检查当前类是否有嵌套内部类
找一个类中的内部类中是否能够满足是配置类的条件。代码如下所示:
@Component
public class AService {
public class A{
@Bean
public BService bService(){
return new BService();
}
}
}
此时的A也是一个配置类,再如下所示:
@Component
public class AService {
@Component
public class A{
public class B{
@Bean
public BService bService(){
return new BService();
}
}
}
}
B也是一个配置类。
但是一般人谁会这样子来写呢?所以一般类上有一个@Component注解即可
2.2.2.3、解析@PropertySources
@PropertySource注解用于指定资源文件读取的位置,它不仅能读取properties文件,也能读取xml文件,并且通过YAML解析器,配合自定义PropertySourceFactory实现解析YAML文件。
将解析出阿里的结果添加到环境变量中去
2.2.2.4、解析@ComponentScans和@ComponentScan
这里就是spring的扫描逻辑了,之前分析过,这里就先不来进行分析
2.2.2.5、解析@Import注解
解析导入的资源注解
1、首先获取得到所有的添加了@Import注解的配置类
这里只是获取得到@Import注解中的value的值,获取得到这里的字符串而已,并没有执行对应的方法
2、然后找到具体的解析方法
然后又将步骤进行拆解
①检查是否存在循环引入
代码如下所示:
@Import(value={B.class})
public class A{}
@Import(value={A.class})
public class B{}
如果存在这种情况,那么spring直接抛出异常信息。
但是这样子做没有任何效果!!!
①@Import使用注意事项
因为是解析配置类过程中,但是配置类当前就只有AppConfig,而没有A和B。
这就表明需要将@Import注解添加到配置类上来
如下所示:
@ComponentScan(basePackages = "com.guang.spring")
@Import(value = {A.class})
public class AppConfig {}
然后不修改上面的A、B的逻辑,可以看到抛出异常:
A circular @Import has been detected: Illegal attempt by @Configuration class 'B' to import class 'A' as 'A' is already present in the current import stack [B->A->AppConfig]
Offending resource: com.guang.spring.service.B
所以一般开发过程中,都是将@Import注解添加到配置类上来,防止失效情况产生。
②找到所有类上添加@Import进行过滤
一、如果候选配置类实现了ImportSelector接口,那么直接通过反射创建对应的对象。然后再次判断是否实现了DeferredImportSelector接口,如果没有实现DeferredImportSelector接口,那么直接调用ImportSelector导入的字符串数组,创建配置类信息,再次来进行解析
二、如果候选配置类实现了ImportBeanDefinitionRegistrar接口,那么直接通过反射创建对应的对象,然后会直接调用Aware方法
然后将对象和对象的源信息保存到ConfigurationClass对象中的importBeanDefinitionRegistrars属性中,方便下次来进行执行。
三、如果既没有实现ImportSelector接口,也没有实现ImportBeanDefinitionRegistrar接口,那么就再次进行查找
2.2.2.6、解析@ImportResource
解析导入的资源文件,这里可以导入配置文件,然后从配置文件中解析得到对应的BD。无非也就是解析XML配置文件而已。
2.2.2.7、解析@Bean方法
解析配置类上的@Bean方法
然后保存到配置类属性beanMethods中
2.2.2.8、处理接口中加了@Bean的默认方法
配置类实现了接口中的默认方法
具体源码如下所示:
也会将对应的默认方法添加到configClass中的beanMethod属性中来
对应的代码如下所示:
public interface UserService {
@Bean
default AService aService(){
return new AService();
}
}
然后在配置类中配置如下:
@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig implements UserService {}
所以这个处理太过于鸡肋,不宜处理。
2.2.2.9、处理父类中加了@Bean的默认方法
对应代码如下所示:
public class BService {
@Bean
public AService aService(){
return new AService();
}
}
对应的配置类修改如下所示:
@ComponentScan(basePackages = "com.guang.spring")
public class AppConfig extends BService {}
通过测试可以看到,在容器中是可以拿到对应的bean的,也就说明了是可以拿到对应的BD的。
2.3、单个BD解析完成
单个BeanDefinition当前配置类解析完成,那么退出之后
循环遍历,直到当前配置类中没有配置类再来进行解析,然后保存到到configurationClasses保存起来。
因为对于还需要对保存到configurationClasses中属性中的集合来做进一步的处理。
如:
1、@Bean保存到configurationClasses中的beanMethods属性中;
2、@ImportResource保存到configurationClasses中的importedResources属性中;
3、实现了ImportBeanDefinitionRegistrar接口的BD保存到configurationClasses中的importBeanDefinitionRegistrars属性中;
需要在最终来进行解析
2.4、递归遍历所有的BD直到不再产生新的BD为止
2.4.1、一轮循环结束对ConfigurationClass做处理
每轮循环结束之后,对保存到configurationClasses中属性中的集合来做进一步的处理。
this.reader.loadBeanDefinitions(configClasses);
如:
1、@Bean保存到configurationClasses中的beanMethods属性中;
2、@ImportResource保存到configurationClasses中的importedResources属性中;
3、实现了ImportBeanDefinitionRegistrar接口的BD保存到configurationClasses中的importBeanDefinitionRegistrars属性中;
三、流程图和配置类扫描总结
参考地址:
1、Spring配置类解析流程
2、配置类解析具体流程