Spring源码分析之ConfigurationClassPostProcessor
前言
在通过Spring源码分析之容器Refresh()方法_spring源码中refresh()方法-CSDN博客我们知到其中有一个步骤就是说会将满足条件的类注册为BeanDefinition然后放入到Spring容器中,这个主要就是存在于invokeBeanFactoryPostProcessors这个方法中进行的这个就是说具体是怎么实现的这篇文章就会进行说明
ConfigurationClassPostProcessor:
这个类在Spring框架起到的作用即使Bean的始源之地,因为我们都知道在Spring启动的时候就会将一些满足条件的配置类注册为BeanDefinition保存到Spring框架中然后再通过BeanFactory才能够开始Bean的生命周期,那么这个注册的逻辑就是在这个类中得到了实现,下面的话就是分析这个类的源码这个主要的核心逻辑就是在postProcessBeanDefinitionRegistry方法中 在刻板的印象中配置类有@Configuration注解的类其实是不正确的还有@component,@import或者@componentScan等注解的java的配置类所以说格局要大一点
下面的话我们主要就是分析这个类里面的postProcessBeanDefinitionRegistry方法中然后就是主要完成配置类的解析的话主要就是通过processConfigBeanDefinitions所以下面的话我们就是来看一看这个方法的源码来进行进一步的分析
processConfigBeanDefinitions
这个方法的源码的话就是进行一步步分析
1.checkConfigurationClassCandidate:
这个方法的话其的主要就是检查这个类是否为配置类其实就是一个注册的前提的工作,这个方法的作用就是给 BeanDefinition 的CONFIGURATION_CLASS_ATTRIBUTE设置了 为full或者lite设置这两个属性标识,那么进行反向思考的话如果一个类满足full或 lite的条件,则会被认为是配置类但是这个两种配置类肯定是不一样的(这个就是留一个疑问)在下面的isConfigurationCandidate方法会进行中解释
//这个就是获得类中configuration注解里面的属性
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
//如果里面的proxyBeanMethods的属性为true但是Configuration里面的这个属性就是true
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
//这个时候就会将这个BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE设置为full
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//如果注解里面的CANDIDATE_ATTRIBUTE属性为true的话
// 或者说isConfigurationCandidate的值为true
else if (config != null || Boolean.TRUE.equals(beanDef.getAttribute(CANDIDATE_ATTRIBUTE)) ||
isConfigurationCandidate(metadata)) {
//就会将CONFIGURATION_CLASS_ATTRIBUTE设置为lite
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
//这个的话就是根据@order注解进行排序
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
那么下面的话就是来看一下isConfigurationCandidate方法的源码:
isConfigurationCandidate
这个方法的作用判断这个配置类的话是不是lite类型的
//这个就是注解的集合
private static final Set<String> candidateIndicators = Set.of(
Component.class.getName(),
ComponentScan.class.getName(),
Import.class.getName(),
ImportResource.class.getName());
static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
//如果是接口的话那么直接返回false
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
//如果被其中的一个注解进行修饰的话那么就是直接返回true
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
//最后的就是如果是检查是否有方法被@Bean注解修饰
return hasBeanMethods(metadata);
}
static boolean hasBeanMethods(AnnotationMetadata metadata) {
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
通过这个源码我们就知道了区别:
full:其实就是代表的就是完整的配置类---@configuration注释的类以及proxyBeanMethods属性为true
lite:这个就是代表的就是 被 @Component@ComponentScan@Import@ImportResource 修饰的类 或者 类中有被@Bean修饰的方法的类
full类型的话就是我们经常使用的配置类 lite类型的话就是通过这个类可以引入其他Bean的类
2.parser.parse(candidates)
上面的方法就是为了检查配置类那么检查完成之后肯定都是配置类那么后面的步骤的话就是对这些配置类进行一次的解析这个主要就是通过
parser.parse(candidates);
下面的话我们就是来看一下这个方法的源码看一看配置类怎么进行解析的
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//这个就是对不同类型的BeanDefinition进行不同的处理
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
parse(annotatedBeanDef, holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
//这个就是实现了自动配置
this.deferredImportSelectorHandler.process();
}
这个里面还出现了pares()方法的话其实就是一个方法的重写,但是最终的都是会执行
processConfigurationClass这个方法下面就是来查看一下这个方法:
//这个就是解析@Cindition注解因为这个的话就是是要在一些条件下进行创建Bean对象的
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 if (configClass.isScanned()) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
//如果已经完成了注册的话那么就会进行移除
this.registry.removeBeanDefinition(beanName);
}
// An implicitly scanned bean definition should not override an explicit import.
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);
removeKnownSuperclass(configClass.getMetadata().getClassName(), false);
}
}
SourceClass sourceClass = null;
try {
sourceClass = asSourceClass(configClass, filter);
do {
//这个就是真正进行解析的工作
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
}
方法的作用: 1.就是看看配置类的话是不是就是需要进行解析(因为@Condition注解) 2.检查已经存在的配置类如果当前以及存在的配置类和要进行解析的配置都是通过@Import注解进行导入的话那么就是进行合并如果exsitingClass不是一个导入的配置类,则忽略新的导入配置类这个就是一个优先级:显性注解的不是导入的>导入的 3.如果这个配置配是被扫描过的话已经注册为BeanDefinition的话那么就会从注册表进行移除这个BeanDefinition 4.如果这个配置类既不是扫描的也不是说导入的话那么这个时候就会替换已经存在的配置类 总而言之就是说是确保配置类及其定义的bean能够根据条件进行正确的解析和注册,避免重复定义不必要的覆盖以及维护配置类之间的正确继承关系
我们会发现这个方法里面就是存在着 doProcessConfigurationClass方法这个我们看源码的时候看见一个方法的前面有一个do的话那么这个时候就是真正的进行一个逻辑的操作了,下面的话我们就是来看看一个到底是怎么个事情
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 1. 处理 @Component 注解
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
// 2. 处理 @PropertySource 注解
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");
}
}
// Process any @ComponentScan annotations
// 3. 处理 @ComponentScan注解
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();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
// 4. 处理 @Import 注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
// 5. 处理 @ImportResource 注解
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);
}
}
// Process individual @Bean methods
// 6. 处理 @Bean修饰的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// 7. 处理其他默认接口方法
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// 处理父类,如果存在
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
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();
}
}
// No superclass -> processing is complete
return null;
}
原来就是处理各种各样的组件注解以及接口和父类啊,其实就是保证置类及其包含的所有注解、方法和依赖关系都能被正确处理就是这么简单,看这源码的话以为有多复杂呢
parser.validate()
这个方法就是说对配置类的话进行进一步的处理源码如下所示: 这个就是说通过
public void validate(ProblemReporter problemReporter) {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
// 获取 @Configuration 注解的属性的信息
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
// 如果 @Configuration 存在(attributes != null) && attributes.get("proxyBeanMethods") == true 才进行进一步的校验
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
// 如果配置类是final 修饰,这个即使不能进行CGLB的动态代理
if (this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem());
}
// 对配置类中的 @Bean 注解修饰的方法进行校验
for (BeanMethod beanMethod : this.beanMethods) {
beanMethod.validate(problemReporter);
}
}
}
beanMethod.validate(problemReporter):
这个就是说对这个配置类里面的有@Bean注解注释的方法进行进一步的解析:
public void validate(ProblemReporter problemReporter) {
//是不是被@Autiwired注解所修饰
if (getMetadata().getAnnotationAttributes(Autowired.class.getName()) != null) {
// declared as @Autowired: semantic mismatch since @Bean method arguments are autowired
// in any case whereas @Autowired methods are setter-like methods on the containing class
problemReporter.error(new AutowiredDeclaredMethodError());
}
//如果没有返回值的话
if ("void".equals(getMetadata().getReturnTypeName())) {
// declared as void: potential misuse of @Bean, maybe meant as init method instead?
problemReporter.error(new VoidDeclaredMethodError());
}
//如果是静态方法的话
if (getMetadata().isStatic()) {
// static @Bean methods have no further constraints to validate -> return immediately
return;
}
Map<String, Object> attributes =
getConfigurationClass().getMetadata().getAnnotationAttributes(Configuration.class.getName());
//这个就是说方法是不是就是被@Configuration注解进行注释
//以及是不是就是可以被重写因为CGLB动态代理的话就是通过重写方法完成的
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods") && !getMetadata().isOverridable()) {
// instance @Bean methods within @Configuration classes must be overridable to accommodate CGLIB
problemReporter.error(new NonOverridableMethodError());
}
}
那么通过parse方法的话我们就是知道就是为了确保每一个配置类都能够被正常地进行解析包括里面中地@Bean注释的方法,那么后面的话我们就是将这个已经解析的配置类的话进行保存那么这个保存set集合当中,但是我们只是将这个配置类进行解析但是没有注册为BeanDefinition后面会说
//将解析成功的配置类就是放在这个set集合中进行存储
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
3.this.reader.loadBeanDefinitions
在上面的话我也说了怎么将这个以及解析成功的配置类进行解析那么这个方法的话就是将这写配置类注册为BeanDefinition通过这个方法的名字的话就是能够看出来对吧
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
这个就是说通过遍历所有的配置类然后就是通过loadBeanDefinitionsForConfigurationClass方法来进行执行的,那么下面的话我们就是来具体看一下这个方法具体就是做了什么事情
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
//如果已经存在这个BeanDefintion的话那么就是从注册表中移除
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
//从导入注册表里面移除这个配置类
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
//这个就是加载@Import引入的类
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//这个就是通过@Bean注解的方法其的返回值进行加载为BeanDefinition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//这个加载@ImportedResource引入的配置类为BeanDefinition
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
//这个加载实现ImportBeanDefinitionRegistrar接口的类
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
看完之后原来如此也就是这样的啊就是说将没有生成的BeanDefintion的配置类的进行相对应的生成,就是说通过配置类中的@Bean方法,@Import等等,原来这个也不过如此
总结:
1.通过 ConfigurationClassPostProcessor 把所有的配置类取出来进行解析
2.那么配置类的标准就是说被 @Configuration、@Component、@ComponentScan、@Import、@ImportResource
修饰的就算是配置类
3.如对@Bean注解修饰的方法的话那么这个时候就是说将这些方法封装为BeanMethod然后后面的话就是将这个方法返回值作为BeanDerfinition
4.如果是@ImportSource注解注释的话那么这个时候就是通过加载XML文件来进行BeanDefinition的生成 如果是@Import注解的话那么就是直接将这个类作为配置类然后就是进行解析
5.如果是@component注解注释的话那么就是会解析类里面的配置
6.如果配置类上存在 @PropertySource 注解,那就会把里面修饰的配置里的k-v放入到Environment中