解析基本流程图
先看下本篇的基本流程图:
ConfigurationClassParser的parse
上次讲到要创建一个ConfigurationClassParser
解析配置类集合,我们来看看他是怎么解析的。
遍历配置类集合,先判断是否是注解类型的,然后是有Class
对象的,最后是只有名字的。然后获取相应的数据进行解析。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {//注解类型
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {//有class对象的
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {//一般的,只有name
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();//处理延迟导入
}
其实内部都是封装成ConfigurationClass
对象的:
//根据className和beanName解析配置文件,需要去URL加载字节码,所以有读取元数据
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
//根据Class和beanName解析配置文件,有Class对象
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
//根据注解元数据和beanName解析配置文件,有注解元数据
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));//封装成一个ConfigurationClass进行解析
}
ConfigurationClass配置类
里面存放着配置相关的注解元数据,被哪个ConfigurationClassimport
进来的集合importedBy
,bean
注解方法信息,ImportBeanDefinitionRegistrar
接口信息等,其实就是来描述配置类的,把我们自定义的配置类解析成ConfigurationClass
配置类。
ConfigurationClassParser的processConfigurationClass
首先会获取有没有ConfigurationClass
存在,如果有的话就看新的是不是import
注解进来的,如果不是就直接把老的删了,如果是,就看老的是不是import
注解进来的,是的话就跟老的合并,不是的话就忽略新的,返回。然后将ConfigurationClass
包装下,里面有原始Class
对象和元数据。然后再进行处理,这里有个循环,递归处理ConfigurationClass
以及其父类,会一直处理父类,返回的父类sourceClass
又会当做新sourceClass
传进去,直到最后是JAVA
内部的父类才停止,最后将ConfigurationClass
放入集合里。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;//有条件注解不满足的返回
}
//获取链表中存在的ConfigurationClass
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {//如果老的和新的都是import的,就合并
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
this.configurationClasses.remove(configClass);//删除老的
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
//获取configClass源类,包装原始的类和元数据
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);//处理配置类,如果有父类(不是java开头的类),继续处理,直到没有父类为止
this.configurationClasses.put(configClass, configClass);//放入集合
}
ConfigurationClassParser的doProcessConfigurationClass之处理内部类
如果注解了Component
,会处理内部类。
//是否注解了Component
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);//处理嵌套
}
ConfigurationClassParser的processMemberClasses以及循环import
获取内部类,如果存在就把候选配置类取出来,然后进行解析,这里有个关键栈importStack
,他会存放要解析的内部类,防止内部类之间循环import
。比如A,B
两个内部类,A
有import
注解,导入的是B
,同样B有import
注解,导入的是A
,这样如果在处理的时候发现存在A
了,那就说明是循环import
了。
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {//防止循环import
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
processConfigurationClass(candidate.asConfigClass(configClass));
}
finally {
this.importStack.pop();
}
}
}
}
}
比如我这样,不过一般不会有人这么写吧,不过写了就被检测出来报异常啦:
于是就会报CircularImportProblem
异常啦啦。
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: A circular @Import has been detected: Illegal attempt by @Configuration class 'MyConfig.T1' to import class 'MyConfig.T2' as 'MyConfig.T2' is already present in the current import stack [MyConfig.T1->MyConfig.T2->MyConfig]
Offending resource: com.ww.config.MyConfig$T1
ConfigurationClassParser的doProcessConfigurationClass之处理PropertySources
这个是跟我们的环境配置文件属性相关的,暂时不是重点,知道就好。
// Process any @PropertySource annotations处理PropertySources
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");
}
}
ConfigurationClassParser的doProcessConfigurationClass之处理ComponentScans
获取ComponentScan
注解,然后解析成bean
定义,最后递归处理配置类,具体细节后面会说。
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());
}
}
}
}
ConfigurationClassParser的doProcessConfigurationClass之处理Import
这里包括Import,ImportSelector
注解和ImportBeanDefinitionRegistrar
接口实现类,会将解析出来的都添加到ConfigurationClass
里,具体代码先不展开,后面会详细说,不然很长了。
// Process any @Import annotations 处理Import
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations处理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);
}
}
ConfigurationClassParser的doProcessConfigurationClass之处理bean注解方法
将bean
注解的方法添加到ConfigurationClass
中。
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));//添加bean注解方法到configClass
}
ConfigurationClassParser的doProcessConfigurationClass之处理接口的默认实现方法
处理接口的默认实现方法,也是进行接口的递归检查。
processInterfaces(configClass, sourceClass);
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
for (SourceClass ifc : sourceClass.getInterfaces()) {
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
for (MethodMetadata methodMetadata : beanMethods) {
if (!methodMetadata.isAbstract()) {
// A default method or other concrete method on a Java 8+ interface...
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
processInterfaces(configClass, ifc);
}
}
ConfigurationClassParser的doProcessConfigurationClass之处理父类
因为有可能父类还有注解定义,所以要寻找父类,直到Java
开头的父类,也就是要递归处理自定义的父类,把父类返回,然后外面继续处理父类。:
// Process superclass, if any如果有父类,且不是java开头的,也是未知的父类,就返回父类
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();
}
}
至此解析自定义的配置类基本完成。
其实我们这样的定义,也是算配置类的,一样可以解析:
比如有bean
方法的:
其实就是前面说的Component,ComponentScan,Import,ImportResource
注解和Bean
方法注解就是配置类,当然也包括Service,Controller,Repository
注解。
中间有很多细节,深入下去需要更多的预备知识,而且篇幅很长,可能会陷了很深,所以没有深入下去,我们还是先把大致的原理搞清楚,细节后面再研究比较好,先观其大略,然后各个击破吧。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。