吐血总结ConfigurationClassPostProces源码解析
2024-02-01 00:18:54 0 举报
"ConfigurationClassPostProcessor"是Spring框架中的核心类,它负责处理配置类,使其成为Bean定义源。该类的主要功能包括: 1. 处理@Configuration注解:当扫描到带有@Configuration注解的类时,将其封装成Bean定义注册到容器中。 2. 处理@ComponentScan注解:当扫描到带有@ComponentScan注解的类时,启动组件扫描,将符合条件的类封装成Bean定义注册到容器中。 3. 处理@Import注解:当扫描到带有@Import注解的类时,处理被导入的配置类或导入选择器,将其封装成Bean定义注册到容器中。 4. 处理@ImportResource注解:当扫描到带有@ImportResource注解的类时,处理导入的资源,将其中的Bean定义注册到容器中。 5. 处理元注解:处理类上的元注解,如@Conditional等,根据条件判断是否将Bean定义注册到容器中。 总之,ConfigurationClassPostProcessor是Spring框架中处理配置类的关键类,通过处理各种注解,使带有这些注解的类能够自动注册到Spring容器中,从而简化了Bean的配置过程。
作者其他创作
大纲/内容
调用每个处理器的init方法实现NamespaceHandler接口的方法有17个
通过类似SPI机制,加载了所有的META-INF/spring.handlers文件的内容
postProcessBeanDefinitionRegistry
getImports(sourceClass)
END
BeanDefinition的实现类。包括AbstractBeanDefinition、RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition、AnnotateGenericBeanDefinition、ScannerGenericBeanDefinition。有空看看todo https://blog.csdn.net/qq_30635523/article/details/131325589
processImports处理 @Import 注解
最重要的org.springframework.context.annotation.ComponentScanBeanDefinitionParser#parse
第二步
todo
通过ASM字节码的方式和反射的方式,得到一个有序的方法集合LinkedHashSet
否,不再处理
return font color=\"#4ccbcd\
包含情况,继续解析
1、解析属性标签base-package(必填字段,requried类似)2、替换里面的占位符3、根据指定字符(\
如果配置是true,默认是true
通过指定的处理器DeferredImportSelectorHandler,处理这个deferredImportSelectors集合
List<TypeFilter> includeFilters = new LinkedList<>();List<TypeFilter> excludeFilters = new LinkedList<>();
上面都是围绕着xml, 让代码飞一会,转眼间。来到了bean的属性填充阶段,来到了beanpostprocessor的执行位置,首先映入眼帘的是ConfigurationClassPostProcessor 也算最帅最复杂最重要的一个
同箭头往上看
loadBeanDefinitionsFromRegistrars
configureScanner方法
画外音:上面的主要工作是寻找符合条件的bean类,然后简单的封装成一个对象,下面主要是完成这个对象的一些类的各种配置,比如作用域,代理模式等,类似一个bean的生命周期类型先实例化--再初始化
往上看
先创建CompositeComponentDefinition对象,然后把上面扫描到的beans放进去。我的理解是,这个对象是装的都是通过注解扫描和其他方式合成的bean,比较特殊,最后就是为了发布事件的时候用,告诉容器到此为止,生成了多少个合成的beans
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.font color=\"#4669ea\
解析每个类,判断是否分别命中excludeFilters和includeFilters
具体来说,当你调用 ClassLoader.getResources(\"META-INF/spring.handlers\") 时,Java 会搜索所有在 classpath 中的 META-INF/spring.handlers 文件,并返回一个 Enumeration<URL>,其中包含了所有找到的资源的 URL。在多个模块的情况下,每个模块都有自己的 classpath,并且这些 classpath 在运行时会被合并。因此,当调用 getResources() 方法时,Java 会搜索所有合并后的 classpath,从而找到所有模块下的 META-INF/spring.handlers 文件。
先创建一个基本的解析器new ClassPathBeanDefinitionScanner
如果包含,并且proxyBeanMethods属性为true默认值:true
namespaceHandler.init();
其他情况
黑盒入口
如果有,开始处理上面注解集合
处理@Bean注解
根据上面的order注解排序,configuration配置类用@order注解可以排序的依据
解析其他 namespace 的自定义元素标签
parser.validate();对解析出来的所有的配置类,进行检查
开始遍历上面的集合
得到的再排序
生成bean的名称,默认是类名小写,,因为平时写的时候一般不写name标签的值
重要
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
情况2、ImportBeanDefinitionRegistrar.class
processConfigurationClass方法
org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
这是spring框架默认自带的所有的命名空间,我们一般只关注其中几个就行,key是命名空间,value是对应的命名空间标签的解析器的全路径,看倒数第二个,加载后,会赋值成实例化对象org.springframework.context.config.ContextNamespaceHandler
this.importStack.pop();出栈
registerBeanDefinition注册beanDefinition
发布事件EmptyReaderEventListener#componentRegistered
生成符合条件的类,包装成对象集合ScannedGenericBeanDefinition
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions
2、AbstractBeanDefinition类型
在Spring框架中,@Component是一个基础注解,用于将一个类声明为Spring组件。除了@Component,还有一系列的衍生注解,这些注解扩展了基础功能并提供额外的特性。以下是一些常见的@Component衍生注解:@Service:这个注解用于标记业务逻辑组件。通常用于标记实现业务逻辑的类。@Repository:这个注解用于标记数据访问组件,如DAO类。@Controller:这个注解用于标记Web应用程序的控制器组件。@RestController:这是@Controller的特殊形式,用于标记RESTful Web服务的控制器。所有方法默认返回JSON或其他的HTTP Response Body。@Configuration:这个注解用于标记配置类,通常与Java配置一起使用,而不是XML配置。在这个类中,你可以使用@Bean注解来声明Bean。@ComponentScan:这个注解用于指示Spring在哪些包中查找带有@Component、@Service、@Repository或@Controller注解的类。如有其他,请你补充,谢谢
判断队列里面是否包含
registerComponents
两个变量: Set<SourceClass> imports = new LinkedHashSet<>(); Set<SourceClass> visited = new LinkedHashSet<>();为下面的递归做准备
return sourceClass.getSuperClass();
是否包含以下注解isConfigurationCandidate
如果当前容器的eanDefinitionCount 大于候选者配置类
涉及一个知识:类加载器,ClassLoader
如果candidates是空,则结束循环
否
解析自定义bean名称生成器标签:name-generator
设置scope
然后遍历结果,再包装一下,放到集合ConfigurationClass#Set<BeanMethod> beanMethods = new LinkedHashSet<>();
执行对象解析器的parse方法
retrieveBeanMethodMetadata
parser.parse(candidates);
同样判断是否跳过,@conditional注解
return null
是否包含Conditional注解
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
注册默认的注解类型过滤器org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters
判断这个配置类是否正在解析中
classpath*:basePackage占位符替换和替换点-->斜杠/**/*.classeg:classpath*:com/liuly/spring/doscan/**/*.class
@Conditonal用法在这里的影响??
ComponentComponentScanImportImportResource
有分组和排序概念,最终还是再调用处理Import注解的方法
如果FactoryMethodName为空
先入栈
遍历候选者集合
process()
得到注解value值及注解value值里面的value值
遍历每个候选者类
1、创建对象AnnotatedGenericBeanDefinition2、设置作用域3、根据bean名称生成器,生成名称4、分别取注解值设置Lazy.classPrimary.classDependsOn.classRole.classDescription.class5、获取注解bean定义类中@scope注解的proxyMode的值判断是否需要生成代理对象如果需要则用ScopedProxyFactoryBean.class包装
最重要的标签component-scan开启注解扫描的意思,1、springmvc xml 注解流行的时候,是需要配置这个标签,才能支持注解扫描(注意不是注解配置,确切的说这是两个概念)2、猜测 springboot 也做了类似内置操作,不用手动配置,才支持注解扫描
BeanFactoryPostProcessor.classBeanPostProcessor.classAopInfrastructureBean.classEventListenerFactory.class
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#handlerMappingsLocationhandlerMappingsLocation的赋值比较特殊,是通过当前类的构造方法赋值的,public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = \"META-INF/spring.handlers\";所以我们也可以自定义这个路径
添加一个后置处理器ImportAwareBeanPostProcessor涉及ImportAware知识点
是
1、判断当前类是不是Import的方式嵌入进来的2、判断是否有子类,是否也是Import类型,递归遍历3、最终还是之前那个评估器的方法,是否有@Conditional注解加持
loadBeanDefinitionsForBeanMethod(beanMethod);
情况1、ImportSelector.class
bean名称:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 类名:ConfigurationClassPostProcessor.class 很重要org.springframework.context.annotation.internalCommonAnnotationProcessorCommonAnnotationBeanPostProcessor.classJPA支持才有org.springframework.context.annotation.internalPersistenceAnnotationProcessororg.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.classorg.springframework.context.event.internalEventListenerProcessorEventListenerMethodProcessor.classorg.springframework.context.event.internalEventListenerFactoryDefaultEventListenerFactory.class
递归判断sourceClass变量是否为空,如果不为空,一直递归
ImportStack importStack = new ImportStack()
解析属性标签:annotation-config
第一步:遍历上面解析到的数组:basePackage
有
candidates.clear();待处理的集合清空
解析标签:scope-resolver
loadBeanDefinitionsFromImportedResources
继续递归
经过上面的筛选,得到一个集合待处理List<BeanDefinitionHolder> configCandidates
1、判断当前方法是否被@Condtional修饰,并可跳过解析2、再次校验是否有@Bean注解3、得到@Bean注解的定义的别名集合4、注册主名称和别名映射5、isOverriddenByExistingDefinition 不懂6、判断是静态的还是实例化的方法①如果是静态方法设置 beanClassBeanClassNameUniqueFactoryMethodName②实例方法:FactoryBeanNameUniqueFactoryMethodName7、设置属性ResolvedFactoryMethod8、设置AutowireMode9、增加属性skipRequiredCheck --->true10、分别取注解值设置Lazy.classPrimary.classDependsOn.classRole.classDescription.class11、分别解析bean的注解属性值赋值autowireautowireCandidateinitMethoddestroyMethod12、解析@Scope注解的属性值 value、proxyMode13、根据上面的12解析值判断是否生成代理对象以及代理模式是CGLIB还是JDK
细节先不看了,就是递归
开始遍历candidates
1、遍历容器内所有的bean,得到full的配置类集合2、校验配置类是否是有的@Bean方法 是被static修饰的,打印警告,跳过3、对剩下的full类,设置代理类beanClass属性
this.componentScanParser = new ComponentScanAnnotationParser(
处理ImportBeanDefinitionRegistrars注解导入的类
解析到全类路径,然后放到缓存org.springframework.core.type.classreading.CachingMetadataReaderFactory#metadataReaderCache
AnnotatedGenericBeanDefinitionConfigurationClassBeanDefinition
递归重要方法sourceClass = doProcessConfigurationClass(
1、ImportStack 入栈2、有可能是一个配置类,被@Configuration 修饰,所以再递归
出栈
1、解析use-default-filters标签,默认是true
创建解析类并配置ConfigurationClassBeanDefinitionReader
无父类
默认情况,会调用默认注册的注解类型匹配器
org.springframework.context.annotation.ConfigurationClassParser#collectImports
处理java8+版本上的,当前类的实现接口默认方法,也有@Bean注解的情况,同样递归调用
拓展
创建两个变量1、放到有序的集合里面,保证顺序和去重,待解析变量2、已经处理的配置类对象
这里面就是撸注解注解分两大类1、类似xml 标签里面的 bean 标签2、类似xml标签里面的 spring xml 方式创建bean的方式1、<bean id=\"exampleBean\" class=\"com.example.ExampleBeanClass\"> <!-- 这里可以定义依赖和属性 --> <property name=\"exampleProperty\" value=\"exampleValue\"/> <constructor-arg type=\"int\" value=\"10\"/> <constructor-arg type=\"String\" value=\"Hello World\"/> <description>This is an example bean</description> </bean>2、<import resource=\"another-config.xml\"/>3、<beans> <bean id=\"exampleBean1\" class=\"com.example.ExampleBeanClass1\"/> -- 算是默认构造器 <bean id=\"exampleBean2\" class=\"com.example.ExampleBeanClass2\"/> </beans>4、使用普通工厂中的方法创建对象,使用某个类中的方法创建对象。并存入spring容器 <bean id=\"instanceFactory\" class=\"com.example.factory.InstanceFactory\"></bean> <bean id=\"accountService\" factory-bean=\"instanceFactory\" factory-method=\"getAccountService\"></bean> 5、使用工厂中的静态方法创建对象,使用某个类中的静态方法创建对象,并存入spring容器 <bean id=\"accountService\" class=\"com.example.factory.staticFactory\" factory-method=\"getAccountService\"></bean>6、<bean id=\"beanThree\" class=\"com.it.app.bean.BeanThree\" depends-on=\
获取Order注解值
分别取注解值设置Lazy.classPrimary.classDependsOn.classRole.classDescription.class
遍历当前类的被@Bean注解修饰的方法
深入了解todo
while循环,判断返回值是否为空
processMemberClasses其实就是解析被注解@Configuration修饰的类的内部类,不管内部类是否有注解,先解析处理,放到候选者集合里
1、实例化当前类2、得到实现类的getExclusionFilter方法3、替换掉默认的过滤器4、判断是不是变体接口的实现类 DeferredImportSelector 推迟导入5、如果是放到集合里面,后续处理List<DeferredImportSelectorHolder> deferredImportSelectors6、执行实现类的方法selectImports,返回一个数组
setLazyInit(lazyInit); setAutowireMode(defaults.getAutowireMode()); setDependencyCheck(defaults.getDependencyCheck()); setInitMethodName(defaults.getInitMethodName()); setEnforceInitMethod(false); setDestroyMethodName(defaults.getDestroyMethodName()); setEnforceDestroyMethod(false);
处理ImportResource.class注解
processConfigBeanDefinitions
解析标签:scoped-proxy
第一步,根据条件评估器,判断当前bean是否需要跳过
判断当前类是否需要跳过
一开始为空,肯定进入下面的方法org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate
展开说todo
如果bean定义对象是属于AnnotatedBeanDefinition类型则得到注解元信息
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
DefaultBeanDefinitionDocumentReader#delegate.parseCustomElement(root);
processImports
创建RootBeanDefinition设置角色int ROLE_INFRASTRUCTURE = 2;代表框架内置的
判断是否有@Component注解. @Configuration注解嵌套了@Component注解
首先是准备工作:给DefaultListableBeanFactory设置一些属性,就是具备后续注解配置解析的能力支持1、dependencyComparator 默认null---AnnotationAwareOrderComparator2、autowireCandidateResolver 默认是SimpleAutowireCandidateResolver 然后注册了这个类的子类,肯定扩展了很多 ContextAnnotationAutowireCandidateResolver
getImports 内部是递归出 注解@Import 的唯一属性value的值并返回
判断注解元信息metadata是否包含Configuration.class注解
得到候选者集合
alreadyParsed集合存放上面已经处理的ConfigurationClass类
处理注解:@PropertySource
创建@Configuration 的解析器new ConfigurationClassParser(
各种解析@Compnont注解属性值,配置这个解析器,和上面的xml方式配置很类似
得到所有的BeanDefinitionNames,逐个遍历解析
属性赋值key:org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClassvalue: full
重要的注册注解后置处理器的位置
也是这个方法
默认值:BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
this.conditionEvaluator = new ConditionEvaluator(
解析标签:resource-pattern
创建ConfigurationClass对象
String namespaceUri = getNamespaceURI(ele);
疑问:1、ConfigurationClassPostProcessor 被谁加载进来的2、ConfigurationClassPostProcessor 开头是处理已经加载到容器的beandifnations,那么是谁先加载呢?4、为啥那么用configuration 注解 而不是用普通注解 @component注解代理,举例真实代码的被configuration注解修饰的类都是怎么写的5、你来问我来答
@Bean public Son son() { Son son = new Son(); return son; } @Bean public Parent parent() { //Son son = son(); Son son = new Son(); return new Parent(son); }上面两行代码是有区别的,第一行注释的是正确的@Bean修饰的方法之间互相依赖的一种方式第二行也是没啥毛病,但是在外部有多次调用parent方法获取Parent对象的时候,获取的Son对象是不同的(不管是full还是lite模式)
处理ImportedResources导入的资源记得之前解析这个集合value是一个解析器BeanDefinitionReader先处理这个解析器,然后走下面的xml方式解析了就,很熟悉,没啥特殊的
主要完成注解AnnotationTypeFilter(Component.class)
开始解析this.reader.loadBeanDefinitions(configClasses);
递归调用processConfigurationClass
1、经过第一轮的洗礼,当前容器的bdNames肯定会有所新增,因为一直往里面注册2、可能注册的的新的bean,正好也是需要被解析的配置类3、所以通过各种过滤,得到下次循环需要的候选者配置类集合4、校验方法,同上一样
eg;返回http://www.springframework.org/schema/context
把当前子类封装成ConfigurationClass,递归调用processConfigurationClass
遍历参数集合
设置一些默认的配置,比如
假如没有xml解析,容器肯定需要注解后置处理器支持,怎么办?入口在哪里? 值得思考
下面有个递归条件,再次调用这个方法
checkConfigurationClassCandidate校验方法同上
registerBeanDefinitionForImportedConfigurationClass(configClass);
public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser(\"property-placeholder\
循环导入异常检查isChainedImportOnStack
invokeBeanFactoryPostProcessors方法入口
判断是否是isConfigurationCandidate 同上,可以搜索这个方法查看并且是和主配置类名称不一样
同样的方法,同上
排除这几个类型的实现类
得到前面init方法加载到hashmap集合对象的解析器,比如标签配置的component-scan
如果命中,则递归继续解析
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser#parse
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
判断是否有父类
配置注解扫描器ClassPathBeanDefinitionScanner
判断属性org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass是否为空注意这是key,很长,意思是这个配置如果有,证明这个beanDefinitionName就已经被解析过了
1、如果bd是AnnotatedBeanDefinition类型
因为上面返回的数组里面的类,也可能是一个Import类,所以把类的重新包装成SourceClass,并且以上面自定义的过滤器,继续递归调用
其他标签解析,你来写 eg: aop/tx
判断方法是否包含Bean.class注解
解析子标签:exclude-filterinclude-filter
PathMatchingResourcePatternResolver
org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate
第一步
BeanDefinitionParserDelegate#parseCustomElement
enhanceConfigurationClasses
loadBeanDefinitionsForConfigurationClass()
如果是导入的类,则进入下面方法
有时间深入了解这个判断的作用
下方递归调用
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
处理注解:@ComponentScans@ComponentScan
1、解析属性值数组:locations2、解析属性值:reader 必须是BeanDefinitionReader类型3、处理locations占位符问题4、遍历1把上面的1和2放到一个Map里面importedResourceskey每个数组值 value 解析器后续再处理
返回,一直往上返回
关注这个对象的两个重要变量
AnnotationConfigUtils.registerAnnotationConfigProcessors
1、生成BeanNameGenerator2、设置StandardEnvironment
IMPORT_REGISTRY_BEAN_NAME不懂
清除缓存metadataReaderCache
org.springframework.context.annotation.ConditionEvaluator#shouldSkip
0 条评论
下一页