Spring——配置类解析过程
在使用的时候还是从下面的一个小demo开始
demo很简单,直接使用AnnotationConfigApplicationContext,传递给他配置类。
在解析配置类的时候,会有下面的两个大的步骤,
- 先将这个配置类作为一个普通的Bean注册到Spring里面。
- 在利用
ConfigurationClassPostProcessor(BeanDefinitionRegistryPostProcessor的实现类)中的postProcessBeanDefinitionRegistry方法来解析配置类,将从配置类解析到的BeanDefinition注册到Spring中。只要赶在调用finishBeanFactoryInitialization方法之前,将bean注册到Spring中就好了。
注册配置类到Spring中
从类图上可以看到,在注册的时候,主要需要三个类,ClassPathBeanDefinitionScanner和AnnotatedBeanDefinitionReader聚合于AnnotationConfigApplicationContext。
在创建AnnotationConfigApplicationContext的时候,这俩就创建出来了。此外,还需要注册,在AnnotatedBeanDefinitionReader里面还需要创建一个ConditionEvaluator(之前在分析@Condition注解的时候已经说了),用来处理Condition注解。此外,还调用了AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);方法,用于注册一些通用的注解配置的处理,如下:
- order的比较器(AnnotationAwareOrderComparator)
- autowire的计算器(ContextAnnotationAutowireCandidateResolver)
- 配置类的解析(ConfigurationClassPostProcessor)
- autowire的解析(AutowiredAnnotationBeanPostProcessor)
- JSR-250 注解的支持和解析(CommonAnnotationBeanPostProcessor)
- jpa的支持(PersistenceAnnotationBeanPostProcessor)
- @EventListener的支持(EventListenerMethodProcessor)
- 用来创建EventListener(DefaultEventListenerFactory)
注意,这些都是在创建AnnotatedBeanDefinitionReader的时候创建的。
对于ClassPathBeanDefinitionScanner,在创建的时候,除了一些常见的赋值操作之外,他还在里面通过useDefaultFiltersbool参数,来控制是否要添加默认的类型过滤器。具体方法在ClassPathBeanDefinitionScanner类的构造方法。ClassPathScanningCandidateComponentProvider#registerDefaultFilters().
注意:上面说的那些都是在创建AnnotationConfigApplicationContext的时候创建出来的,并且AnnotationConfigApplicationContext实现了BeanDefinitionRegistry接口。那俩就可以用他来注册BeanDefinition了。
最终是通过调用AnnotatedBeanDefinitionReader#register方法将Bean注册到BeanFactory中。因为AnnotationConfigApplicationContext的register方法是可以传递一个可变的数组的。最终的实现就是循环调用,将配置类注册到BeanFactory中。
方法解析:
总体思想:将这个配置类,作为一个普通的bean来解析,并且注册到BeanFactory中。
- 创建AnnotatedGenericBeanDefinition,在创建它的时候会通过
AnnotationMetadata.introspect(beanClass);拿到这个类上面的元信息(元信息是包括他的类信息,注解信息,接口信息,字段信息等等)。具体的可以看AnnotationMetadata类的具体实现 - 因为他也是一个普通的bean,也有可能有@condition注解,所以,得先通过
conditionEvaluator.shouldSkip(abd.getMetadata())来判断@condition注解。 - 处理@Scope注解。
- 处理bean信息的一些基本的注解,比如Lazy,Primary,DependsOn,Role,Description。
- 处理BeanDefinitionCustomizer,这个参数是可以通过
AnnotationConfigApplicationContext#registerBean方法传递进来的。 - 封装成一个BeanDefinitionHolder。处理scope注解里面proxyModel参数。注册到beanFactory中。需要注意一下
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry)方法,在这个方法里面会通过scope的proxyModel创建合适的代理对象。
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// 创建AnnotatedGenericBeanDefinition,需要注意,在创建它的时候,会获取这个类上面的元信息,
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 计算@condition
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
// 实例的提供者,
abd.setInstanceSupplier(supplier);
// 拿到scope注解的metadata
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
// 单利还是多例还是别的
abd.setScope(scopeMetadata.getScopeName());
// 生成个名字
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// 处理通用的一些注解信息,比如@role
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
//
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
// 应用scope注解里面的proxyModel的模式。
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
利用ConfigurationClassPostProcessor来解析配置类。
方法的调用是在AbstractApplicationContext#refresh里面,这个方法是Spring的核心方法。
在准备好BeanFactory之后,就会调用AbstractApplicationContext#invokeBeanFactoryPostProcessors方法,在这个方法里面会调用所有的BeanFactoryPostProcess,关于这个方法就不在这里全部贴出来了,说一下大体的流程吧。
-
在AbstractApplicationContext里面有一个beanFactoryPostProcessors属性,存放的是所有BeanFactoryPostProcessor的集合。与此同时,Spring提供了对应的add方法。所以,完全有可能,在调用ApplicationContext的时候,添加一些BeanFactoryPostProcessor。如果不添加的话,一开始这个肯定是没有的,因为那些bean还是BeanDefinition,所以,在方法的一开始,就需要兼容这种情况。先调用beanFactoryPostProcessors集合里面的bean。
-
主要的思想,就是调用BeanFactoryPostProcessor。但是这里有几个情况,首先是
- Ordered注解和PriorityOrdered注解的优先级。
- BeanDefinitionRegisteryPostProcess和BeanDefinitionPostProcess的优先级。
按照上面的分析,BeanDefinitionRegisteryPostProcess的优先级高,PriorityOrdered的优先级高。
-
需要注意的是,在Spring里面甭管定义的那种类型的Bean,都要声明的,早些时候的XML,现在的注解@Component。都是。除了FactoryBean创建的对象。所以,它也不例外,也得定义在Spring中,并且使用xml或者@Component注解声明。Spring才能发现。
-
创建BeanFactoryPostProcessor的时候也是需要走标准的Bean的创建过程的。也就是说,那些init方法,aware接口,destroy方法,都会应用。(在调用refresh方法的时候,Bean的所有的定义信息已经添加到BeanFactory中了,但是这个时候还是可以继续添加的,直到preInstantiateSingletons之前)。
问题?
如果在一个BeanPostProcess里面需要一个自己定义的Bean,会怎么样?会导致这个bean提前初始化?能用@Autowire注解吗?
这样是可以的,是可以用的,会到这个引用的这个bean过早的实例化。
不能用@Autowire注解,可以用构造器注入.理论上来说,走Spring标准的创建bean的,就会有属性注入,但是@Autowired注解的解析是在
AutowiredAnnotationBeanPostProcessor里面做的,它是一个BeanPostProcessor。这是在调用完BeanFactoryPostProcess之后才初始化的,在BeanFactoryPostProcess里面使用是不可以的。但是构造器的注入是可以的。因为构造器的注入是发生在创建bean的时候。这个时候已经很明确的知道了,创建这个对象需要什么参数,Spring就会doGetBean了。这个时候依赖的对象就创建出来了。
postProcessBeanDefinitionRegistry
主要功能:
解析配置类,并且将解析出来的beanDefinition,注册到SpringFactory中。
这个方法最终会调用到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法里面。在这个里面开始做解析。本文的重点来了。这里贴代码过来分析分析.
方法解析:
大体分为三个个步骤
第一:就是找到@Configuration注解标注的配置类。(肯定要遍历,通过上面说的Metadata来获取注解的元信息,检索。)
第二:使用配置类的解析器(ConfigurationClassParser)将配置类解析了。
第三:使用beanDefintionReader读取配置类,变为BeanDefinition,并且注册到Spring中。
具体的操作看代码注释。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
// 开始遍历
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 检查属性中是否有CONFIGURATION_CLASS_ATTRIBUTE。CONFIGURATION_CLASS_ATTRIBUTE属性有点特殊,在后面的增强配置类的时候用的着。
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 检查是否是Configuration注解修饰的类。看到这个metadataReaderFactory。就知道肯定是通过Metadata来检查的
// 此外,还设置了beanDefinition的一些数据(根据proxyBeanMethods还有Order注解)
// 还有,BeanFactoryPostProcessor,BeanPostProcessor,AopInfrastructureBean,EventListenerFactory的bean都直接返回 / //false
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 如果没有配置类就直接返回。
if (configCandidates.isEmpty()) {
return;
}
// 排序,按照自然顺序来
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 拿到BeanNameGenerator。
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// 重点来了,创建解析器,来计息配置类。
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 一开始的遍历集合
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 最终的结果
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解析和验证
parser.parse(candidates);
// 解析出来的配置类(ConfigurationClass)对象,循环遍历,@Configuration中的proxyBeanMethods为true就需要校验。
// 如果需要校验,配置类不能为final,并且@Bean标志的方法不能被重写。
parser.validate();
// 最后,会将解析好的配置类,封装到ConfigurationClassParser#的configurationClasses里面。
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 做加载。
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 从解析好的配置来,解析Beandefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// 这里有一个小小的tips,如果现在比之前的多,说明配置类是真的将一些bean注册到BeanFactory中。
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
// 如果新的导的bean是一个配置类,就会继续解析。一直等到candidates为空
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
//注册两个bean。
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
从上面的代码已经显示出了主体的逻辑了。上面有两个关键的类,ConfigurationClassParser(解析配置类),ConfigurationClassBeanDefinitionReader(从配置类中注册BeanDefinition)。
ConfigurationClassParser
parse方法分析
方法的入参是候选的配置类集合,循环开始解析,在这个方法里面通过不同的参数类型最总会调用到ConfigurationClassParser#processConfigurationClass方法里面。并且会将配置类封装为ConfigurationClass对象,如下所示:
它里面的属性,其实代表的就是配置类的各种能用的信息。这个比较重要,在解析的过程中,就是将解析到的东西,放在对应的属性里面。这个概念贯穿配置类解析。
下面分析processConfiguClass的具体操作。这个方法是很重要的方法,并且会多次的调用。前面说了,在解析配置类的时候,会封装为ConfigurationClass对象。
- 因为配置类上面也是有@condition注解的,所以,得先判断。
- 因为这个类是解析配置类的,并且这个方法会多次的调用的。多次调用就意味着,可以做一个缓存。这里的configurationClasses就是
- 这里还将ConfigurationClass再次封装为SourceClass。
- 递归开始解析,解析他的父类。
- 最后放入缓存中。
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 计算@Condition注解
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 检查缓存
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// 递归解析父类
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
// 放置缓存。
this.configurationClasses.put(configClass, configClass);
}
问题?
这里为什么要将ConfigurationClass再次变为SourceClass?
ConfigClass是一个配置类的最终的表现形式,但是SourceClass只是在解析配置类的时候中间用到的,此外,他的这种表示方法不用管SourceClass到底是怎么加载的,并且可以用这个对象来做统一的管理
到了do开头的方法了,真正的做事情的方法。
注意,这个方法的最后一个参数我还没有解释,
filter
默认的就是长这个样子,这个filter只要是用在构建SourceClass对象上面,如果疲惫,返回的就是一个Object。不是真正的bean了。也就是说这个bean会用一个Object代码,在之后的sourceClass中
(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));
这个方法是真正开始解析配置类,想想,在一般在使用配置类的时候有什么注解,这里就会逐个解析。在参照之前说的ConfigurationClass的成员变量。就能有一个整体的思路。
具体看看注释
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 如果这个配置类是一个被Component修饰的方法。就会解析他的内部类,因为内部类也有可能是一个配置类
// 所以还得在走一遍上面说的那个重要的方法,processConfigurationClass方法,将内部类封装为一个ConfigurationClass
// 来解析
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// 解析PropertySources注解。这里是没有递归解析的,这个注解的作用就是加载属性文件,添加到environment中,
// 可以看到,条件满足的时候把ConfigurableEnvironment对象传递进去了,这里面做的事情就是解析先解析出文件的位置
// 组成CompositePropertySource,把他添加到MutablePropertySources里面,MutablePropertySources就是一个list。
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 处理ComponentScan注解,至于@ComponentScan扫描的过程,这里就不说了,不是本文的重点,之后专门说这个
// 这里再次调用了conditionEvaluator来计算@Condition,注意了。
// 是用componentScanParser从指定的包里面开始扫描bean,并且把它们封装为BeanDefinitionHolder。
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 这里对扫描出来的这些bean,再次做个判断,看是否是配置类,因为谁也不知道,这次扫描出来有没有配置类。
// 既然是都是配置类了,就直接调用上面说的解析配置类的方法了,再次调用(processConfigurationClass)
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 解析@Import注解。
// 其实这里的解析,也有的说,日常怎么用@import注解的,这里就怎么解析。
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 解析@ImportResource注解。这个注解就是导入一些配置资源,比如xml配置的bean。
// 这里没有做解析,做导入,只是将他记录为一个map,map的key是配置文件的名字,v是beanDefinitionReader。
// 如果是 .groovy文件会通过GroovyBeanDefinitionReader, 其余的都是XmlBeanDefinitionReader.
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 解析@Bean方法,只要是通过AnnotationMetadata#getAnnotatedMethods方法来做的。
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 解析接口中方法修饰符为default的方法,这里直接是获取接口集合,编辑集合,检索接口中标注了@Bean的方法,并且这个方法不是抽象的
// 这里有一个递归的调用、
processInterfaces(configClass, sourceClass);
// 解析他的父类
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
//父类不能是java开头的,也就是说,对于java提供的对象,不适合,一个小小的注意点。
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// 没有父类的话,直接返回,外面的while循环条件不满足,直接退出,解析配置类到此结束。
return null;
}
有一个小的tips,在分析解析的过程中,要将实际使用结合起来,这样分析起来很清晰。大体的逻辑就出现了
下面我要着重分析的是@import注解。 这是实现Springboot starter的一个很重要的点
回想,在日常使用@Import的时候,是怎么用的?第一映像肯定是导入一个配置类,这没有错,他也可以导入一个普通的Bean、话说回来,配置类也是被@Component注解修饰的。🐶
此外,在导入bean的时候,除了上面说的配置类的bean和普通的bean这两种大方向上的不同的类型之外。还有两种特殊类型的Bean.
-
ImportSelector
导入的一个选择器,可以基于配置类的AnnotationMetadata来做判断,返回合格的需要导入的bean的class的名字(全类名),并且提供了一个Predicate来做一个先前的判断。
-
ImportBeanDefinitionRegistrar
这个直接是可以在BeanDefinitionRegistery里面注册BeanDefinition的。直接注册就完事了。
这俩是Springboot实现自动注入的重点,ImportSelector,ImportBeanDefinitionRegistrar
下面继续分析处理@Import注解的方法逻辑。
注意这个参数:importCandidates、这里面就是@Import注解需要导入的类的信息,对应的代码是 ConfigurationClassParser#getImports。
会将需要导入的类的信息,封装为一个SourceClass对象。在这里做导入。
具体的代码逻辑如下:
-
先看需要导入的类的集合里面有没有。
-
在来一个栈,用来检测循环导入的问题,比如A导入B。B导入A。
-
处理两种特殊类型
-
ImportSelector
先实例化,他的构造函数可以有Environment,BeanFactory,ClassLoader,ResourceLoader并且还可以实现几个aware接口。EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware,ResourceLoaderAware。
调用getExclusionFilter,和之前的exclusionFilter组成或的关系。调用selectImports,返回全类名。组成ConfigClass的集合,再次调用processImports。对于DeferredImportSelector,不会立即调用,而是放在一个集合里面(在解析完了之后才会调用)。注意,这里并没有将ImportSelector的实现类,放在ConfigClass的属性里面,也没有添加到BeanFactory里面。
-
ImportBeanDefinitionRegistrar
实例化的过程还是和上面是一样的。但是这里却把他添加到ConfigClass的importBeanDefinitionRegistrars里面。
-
-
剩下的就继续调用processConfigurationClass来解析,并且将他们包装为ConfigClass对象,并且会设置ConfigClass的ImportBy属性为当前的这个configClass对象。具体看下面的这个方法。candidate.asConfigClass(configClass)。
具体的逻辑看注释。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 如果不需要导入,直接返回
if (importCandidates.isEmpty()) {
return;
}
// 这是一个循环检测的标志位置,防止A导入B。B导入A这样的事情发生。
// 需要的做法就是放在一个栈里面,在处理之前先检测。如果有,就报错
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
// 进栈
this.importStack.push(configClass);
try {
//开始循环。处理需要导入的类
for (SourceClass candidate : importCandidates) {
// 是否是ImportSelector
if (candidate.isAssignable(ImportSelector.class)) {
// 拿到Class对象,这个Class对象就是ImportSelector的实际生成的对象
Class<?> candidateClass = candidate.loadClass();
// 实例化,从这里可以看到,在ImportSelector的实现类里面,构造方法可以有environment,resourceLoader,registry
// 同时,点进去看这个方法,他还调用了Aware方法。
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// 拿到不需要的Bean的判断的Predicate
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
// 和之前的组成或的关系
exclusionFilter = exclusionFilter.or(selectorFilter);
}
// 如果是DeferredImportSelector,就会将这个ImportSelector添加到deferredImportSelectorHandler。这其实就是一个DeferredImportSelector的集合。
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 否则,就是直接调用,这个返回的是bean的全类名。
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//这里会将这些全类名组成的集合,加载,并且获取他的Metadata。封装成SourceClass对象。
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
//继续走一遍导入的逻辑,也就是说 ImportSelector,返回的完全可以是一个ImportSelector,并且不会处理循环导入的问题。或者也可以是一个ImportBeanDefinitionRegistrar。
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
//看到这里,需要注意一个事情:(这里除了DeferredImportSelector会被添加到deferredImportSelectorHandler里面去,别的,并且它还不是ConfigClass的属性,别的可没有添加在里面,调用完直接就么得了。没有放在Spring容器里面。)
// 如果是ImportBeanDefinitionRegistrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 和上面一样的操作,实例化,调用aware方法
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
// 这里实例化了之后,并没有直接的调用,而是放在ConfigClass里面的属性里面(importBeanDefinitionRegistrars)
// 这是一个LinkHashMap,key是ImportBeanDefinitionRegistrar,v是当前解析的类的Metadata对象
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 如果不是上面两种的特殊类型了,因为导入完全可能还是一个配置类,所以,再来一遍processConfigurationClass方法。
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// asConfigClass方法会创建一个新的ConfigClass,并且还会设置ImportBy属性为当前的这个ConfigClass
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
到这里,解析pare方法就结束了,这个方法的主要中的作用就是和开头说的一样, 封装为ConfigClass对象,并且设置里面的属性,要注意ConfigClass里面是否有关于ImportSelector的属性。他不会放在ConfigClass中,那我我们的思路要从parse方法跳出来,接着往下看。
但是有个问题没有看DeferredImportSelector的调用
这个其实是在parse方法结束的时候调用的,这里我省略掉了。🐶
继续往下走,就走到parser.validate();了。验证的步骤很简单,在processConfigBeanDefinitions代码注释里面已经写了。
下面就开始走到ConfigurationClassBeanDefinitionReader的部分了。
ConfigurationClassBeanDefinitionReader
在上一步将配置类,封装为ConfigClass对象之后,现在要开始做加载了,得将它们变为BeanDefintion了。
第一步还是先创建ConfigurationClassBeanDefinitionReader,从loadBeanDefinitions#loadBeanDefinitions进去,一直点点,就能看到下面的代码(主要是将上一步创建好的ConfigClass对象的的集合传递过来,循环遍历调用下面的这个方法)
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 第一步 还是先计算@Condition,这一步的ConfigurationPhase就是REGISTER_BEAN了。
// 此外,在分析@Condition的时候说过,这里面会处理连带处理@Import。只要他的ImportBy的@Condition有一个不行,他就不会导入。
if (trackedConditionEvaluator.shouldSkip(configClass)) {
//
String beanName = configClass.getBeanName();
//先移除可还行,通过名字,可还行
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
//
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// 如果当前的这个ConfigClass是被导入的
if (configClass.isImported()) {
// 注册到SPring容器中
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//加载@Bean标注的方法,将bean方法解析的过程比较多,后面搞一个@bean方法创建bean分析的文章在介绍介绍
// 现在要知道,这也是解析为BeanDefinition注册到容器里面,但是这里面有几个重要的点,在下面说
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//处理ImportResource注解,key是配置文件的名字,value是对应的处理类,如果是.groovy结尾的,就采用GroovyBeanDefinitionReader,否则就是XmlBeanDefinitionReader
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//处理之前说的ImportBeanDefinitionRegistrar,其实就是循环调用。
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
@Bean方法解析过程
-
因为@Condition还可以用在方法上,所以,这里要在计算一次@Condition。但是ConfigurationPhase是REGISTER_BEAN。
-
spring好的一点是他封装的特别好,一个metadata可以是方法,有可以是类,所以,方法也有自己的metadata对象(MethodMetadata),获取@Bean的属性,并且处理。这里面有一个有意思的点
-
关于bean的名字
@Bean中name属性是一个数组,名字是一个数组,这可不行。所以,spring的处理是,如果有,就采用数组的第一个元素,作为bean的名字,其余的都是别名,并且如果没有指定的话,方法的名字就是bean的名字。
-
-
还会检查当前的bean是否和之前的在Bean工厂中已经存在的Bean名字一样@Bean所在的配置类的名字也是一样的。
-
对方法的修饰符是没有关系的,比如private,public,等等,只是对static有一点点的变化,别的都是可以的,并且会将当前的方法作为创建这个Bean的工厂方法,并且还会解析Scope注解的proxyModel。
-
其余的就是解析@bean里面的属性了。最后注册到里面。
这里只是对@Bean方法说了一个大概,之后会分析这个具体的过程,以及@Autowire注入的实现。
继续按照主线来看,在注册完BeanDefinition之后,会有一个前和后的数量,找出,在Register的时候注册进去的配置类,再来一次解析。
因为在在这个过程中,很有可能注入的bean里面是有配置类,比如,xml可以配置一个配置类,在类上面有@Configuration注解,或者@Bean方法注入的bean也可以有,再者ImportBeanDefinitionRegistrar也是可以导入的。
到这里,解析和注册为BeanDefinition已经完成了。
下面的内容中有一个用到的知识点,在这里先分析分析,这个在上面的代码出现过,但是我没有详细的说
ConfigurationClassUtils#checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory)
首先ConfigurationClassUtils就是一个为Configuration类的一个工具类
这个方法主要受判断当前的这个Bean是否是一个配置类,并且会设置bean的属性。
我想说的是@Configuration注解里面的proxyBeanMethods的属性,默认为true,会设置BeanDefinition的属性CONFIGURATION_CLASS_ATTRIBUTE,它在下面的章节中是有用的。
这里就不介绍他的作用了,在下面的章节会介绍。
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
// 下面的几个类是不能作为配置类来做的
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
// 主要是在这里从bean的metadata里面获取Configuration注解
// 并且通过注解中的proxyBeanMethods来设置bean的属性,CONFIGURATION_CLASS_ATTRIBUTE。
// proxy默认为true:值为CONFIGURATION_CLASS_FULL,否则就是CONFIGURATION_CLASS_LITE
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// 获取order注解,并且设置属性
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
postProcessBeanFactory
主要功能:
通过用CGLIB增强的子类替换配置类,为运行时服务bean请求做好准备。
首先他作为一个BeanFactoryPostprocess,肯定要看这个方法里面具体是怎么做的。
这里就是遍历所有的BeanDefinition,找出属性有CONFIGURATION_CLASS_ATTRIBUTE,并且是CONFIGURATION_CLASS_FULL的,放在一个集合里面,这里最主要的方法是利用ConfigurationClassEnhancer来创建一个代理的class,并且将这个配置类里面的BeanDefinition里面的BeanClass设置为代理的类。(做代理了),如果@Configuration的proxyMethods为false,就不会创建代理。
问题?
为啥要做代理?
其实他这个本质的目的就是为了解决方法嵌套生成多个Bean的操作,比如现在有A和B两个方法,都标注了@Bean注解,如果A方法里面调用B。那在创建A的时候会不会将B添加到容器里面,肯定不会,那在调用B方法的时候,会创建B吗?会。并且会添加容器里面,那么问题就来了,B创建了两次。所以,这里的目的是为了解决这种方法嵌套的时候创建Bean的问题的。
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
....... 省略掉了
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
logger.info("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
// 开始做代理了,重点是在于ConfigurationClassEnhancer类里面怎么做的,
// 并且,这里还设置了AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.getBeanClass();
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
beanDef.setBeanClass(enhancedClass);
}
}
}
ConfigurationClassEnhancer
生产代理Configuration对象的代理对象,使用cglib来生成子类的方式来获取代理对象。重写了@Bean的方法,当真正的调用@Bean方法来创建对象的时候,这个对象才会被SPring容器来创建,否则,都是给代理对象来操作的,那么这个操作就是去容器中查找有没有请求的Bean。
首先说明,这个方法本身很简单,想想在使用cglib的时候也是很简单的,设置superClass,设置callback,设置filter。然后一个create。就生成了,同样的道理,代理对象因为cglib和proxy(java)变的很容易,重点在于目标方法的前和后干了什么事情。
那么下面我们来详细的看看这个类。
既然是cglib,就要看他添加的几个回调里面干了什么事情,如果有多个回调的话还得看他的CallbackFilter。
Callback
他是有三个callback的操作的,主要的就两个,具体的代码分析就不做了,因为太多了,下面说说大体方法的作用和一些注意的地方,具体的方法在
org.springframework.context.annotation.ConfigurationClassEnhancer类里面,找到上面图示的属性。点进去看对应的类的实现。
问题
既然是要从BeanFactory中检索Bean。那BeanFactory是在啥时候注入进去的?,对应的字段名字叫什么?
首先,在ConfigurationClassEnhancer里面有EnhancedConfiguration接口,他是一个继承了BeanFactoryAware,是一个空接口。他让代理类实现了这个接口,在创建代理对象的时候。具体的代码在ConfigurationClassEnhancer#newEnhancer方法里面。
既然要用,就要知道叫什么名字,这样才能获取到,字段叫beanFactory的字段。
啥时候设置进去的?要知道,cglib的代理是生成字节码,然后在内存里面加载,从而生成代理对象的。那肯定是在生成字节码的时候写进去的。具体是在ConfigurationClassEnhancer#BeanFactoryAwareGeneratorStrategy类里面。这是cglib提供的一个钩子函数。transform方法了里面会在给代理类生成一个名字为$$beanFactory,类型是BeanFactory的字段。
在ConfigurationClassPostProcessor的postProcessBeanFactory里面添加了ImportAwareBeanPostProcessor(InstantiationAwareBeanPostProcessorAdapter的实现类)。主要是重写了两个postProcessProperties和方法里面判断,判断当前的bean是否是一个EnhancedConfiguration,然后设置BeanFactory。
在设置的时候会调用到BeanFactoryAwareMethodInterceptor的intercept方法。这里面会通过反射来设置值。
BeanMethodInterceptor
拦截@Configuration类中,标注了@Bean的方法,他能确保正确的处理Bean的操作。比如Scope和Aop的代理。
就是说,在调用@Bean方法之前,这个会起作用,会检查需要的这个bean是否在BeanFactory中,有的话,就直接从BeanFactory中返回,否则就走标准的创建的流程,其实他这个本质的目的就是为了解决方法嵌套生成多个Bean的操作,比如现在有A和B两个方法,都标注了@Bean注解,如果A方法里面调用B。那在创建A的时候会不会将B添加到容器里面,肯定不会,那在调用B方法的时候,会创建B吗?会。并且会添加容器里面,那么问题就来了,B创建了两次。所以,这里的目的是为了解决这种方法嵌套的时候创建Bean的问题的。这就有个问题了.为什么用cglib,不用Java的Proxy呢?
原因我觉得有下面几个:
- cglib比Proxy功能强大。一个是类,一个是接口。
- 写配置类的时候不能要求用户强制实现一个接口吧。并且接口里面的方法还是得规定好的,这显然不可能。
- 如果说强制实现一个接口是允许的话,java的Proxy是不能发现类里面方法之间的调用的。但是Cglib是可以的。具体之后再说。
好了,继续看看。
BeanFactoryAwareMethodInterceptor
拦截@Configuration类中任何的BeanFactoryAware#setBeanFactory(BeanFactory)方法,为了可以记录BeanFactory。
NoOp.INSTANCE
不处理,啥都不做
CallbackFilter
没啥可说的,在创建它的时候就把callback的数组设置了进来,在调用的时候,将权限下放给了ConditionalCallback
到这里分析已经分析完ConfigurationClassPostProcessor对于@Configuration注解的解析操作。
总结如下:
主体分为两个部分:解析和加载。创建配置类的代理对象。
将配置类解析为ConfiClass对象,将@Configuration类里面能用的注解都封装在里面,这里面会设置到@Condition注解的判断,@import注解的处理,@Bean方法等等,在解析完之后,开始加载,加载就是创建通过之前弄好的ConfigClass来创建对应的BeanDefinition,并且注册到BeanFactory中。整个起作用是在BeanFactory创建好之后,在正常的BeanFactoryPostProcess之前的postProcessBeanDefinitionRegistry方法中做的。
在BeanFactoryPostProcess方法里面会创建配置类的代理对象,这是通过@Configuration注解的ProxyMethod来做的。默认是要创建的。他是主要通过Cglib生成配置类的子类,做代理,做代理的目的是为了防止方法嵌套调用产生Bean对象不一致的问题,主要是增加了两个拦截器,BeanFactoryPostProcess和BeanFactoryAwareMethodInterceptor,在调用的时候会先看当前的bean是否在创建,如果在创建,就调用父类(也就是正常的创建对象的操作,)如果没有,就直接从BeanFactory中获取Bean。这样,就能保证在方法嵌套调用的时候产生的bean在Spring中是唯一的。
方法嵌套调用的解释:
比如现在有一个配置类:
有两个Bean的方法,TestBean需要一个TestBean1。我在testBean里面调用testBean1方法。这就是解决这个问题的。如果方法嵌套调用产生的对象不一样,那不就出现问题了吗?
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class config {
@Bean
public TestBean1 testBean1(){
TestBean1 testBean1 = new TestBean1();
testBean1.setName("testbean1");
return testBean1;
}
@Bean
public TestBean testBean(){
TestBean testBean = new TestBean();
testBean.setAge(12);
testBean.setName("testBean");
TestBean1 testBean1 = testBean1();
testBean.setTestBean1(testBean1);
return testBean;
}
}
测试
try (
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class);
) {
TestBean bean = context.getBean(TestBean.class);
System.out.println(bean);
TestBean1 bean1 = context.getBean(TestBean1.class);
System.out.println(bean1);
} catch (Throwable e) {
e.printStackTrace();
}
结果
可以看到,两个地方的的TestBean1对象都是一致的。
如果让他不要生成代理的配置类,会怎么样,上面问题的结果是什么?试试变为@Configuration(proxyBeanMethods = false)
在看看结果
可以看到,在方法嵌套里面的TestBean1没有被Spring管理,(这里的意思是Spring和他没有一点点的关系,直接就没有放在Spring里面)。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。