Spring 核心类 ConfigurationClassPostProcessor 注入流程以及相关的源码解析如下,首先对其类的注入来源进行详细分析
介绍
在 XML 配置容器方式中,一开始对 XML 文件进行解析,新加了一层层注解 @Configuration、@Bean、@Component 等等,解析&加载的实现,但是其并不影响 Spring 底层的实现,从中体现了 Spring 中强大的扩展性
例如:在 XML 配置【<context:component-scan base-package="com.vnjohn.config">
】 以后,通过 ComponentScanBeanDefinitionParser
解析 XML 自定义标签时,会扫描配置的包下所有的类文件,将带有 @Component【@Service、@Controller 这些注解内部都引入了它】 注解的类都找到,加载为 ScannedGenericBeanDefinition【GenericBeanDefinition 子类】
类,放入到 beanDefinitionMap、beanDefinitionNames 集合中,等待 Bean 实例化过程进行加载
在扫描 XML 标签:<context:component-scan>
时,会新增一些注册处理器,它们负责后面的解析、加载工作,其中就承担了常用注解的解析工作(@Component、@ComponentScan、@Configuration、@PropertySource、@Import、@Bean)
以下是介绍在 SpringBoot 中 ConfigurationClassPostProcessor 类是如何被注入的过程
- SpringBootApplication#run 内部会创建 ConfigurableApplicationContext 接口下某个实例,Web 下创建的类就是它的实现类 AnnotationConfigServletWebServerApplicationContext
- 随即它的构造方法里面就会创建 AnnotatedBeanDefinitionReader 实例
- AnnotatedBeanDefinitionReader 构造方法里面又会调用:AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
- 此时就会创建好 ConfigurationClassPostProcessor 该类的 BeanDefinition 信息
- 在执行
invokeBeanFactoryPostProcessors
方法时会获取到 BDRPP 的实现类【ConfigurationClassPostProcessor & 实现了PriorityOrdered
】 - 至此,该类 bean 实例就已经创建好了,并且会将标注的相关注解进行 beanDefinition 信息的创建,后续进入 Spring Bean 生命周期的过程中会将这些 BD 进行实例化、初始化,从而完成相关 Bean 创建工作
配置类加载过程
ConfigurationClassPostProcessor#postProcessBeanFactory:enhanceConfigurationClasses 增强所有 @Configuration 修饰的配置类,生成代理类,为了确保其下的 @Bean 方法是能够保持单例的
、添加一个新的 ImportAwareBeanPostProcessor,BPP 后置处理类
在介绍 ConfigurationClassPostProcessor 类的执行流程时,先对 @Condition 注解使用进行以下说明,因为在执行过程中会涉及到该注解的逻辑
@Condition 注解使用
类或方法上增加了 @Condition 注解,在解析注解配置时会调用 shouldSkip
方法,基于 @Conditional 判断该对象是否要跳过,如果不满足 @Conditional 中 value 中的条件,就跳过该 Bean,不会注入容器
value 中的条件是具体的匹配逻辑,可以设置多个,如果匹配返回 true:代表可以正常注入,返回 false:代表不会注入容器中
代码示例如下:
// bill 会正常注入,linux 和 windows 会按照条件去匹配是否可以注入 @Conditional({WindowsCondition.class}) @Configuration public class BeanConfig { @Bean(name = "bill") public Person person1() { return new Person("Bill Gates", 62); } @Conditional({LinuxCondition.class}) @Bean("linux") public Person person2() { return new Person("Linux", 48); } @Conditional(WindowsCondition.class) @Bean("windows") public Person person3() { return new Person("windows", 50); } } public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext.getEnvironment(); String property = environment.getProperty("os.name"); if (property.contains("Linux")) { return true; } return false; } } public class WindowsCondition implements Condition { /** * @param conditionContext:判断条件能使用的上下文环境 * @param annotatedTypeMetadata:注解所在位置的注释信息 */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // 获取ioc使用的beanFactory ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory(); // 获取类加载器 ClassLoader classLoader = conditionContext.getClassLoader(); // 获取当前环境信息 Environment environment = conditionContext.getEnvironment(); // 获取bean定义的注册类 BeanDefinitionRegistry registry = conditionContext.getRegistry(); // 获得当前系统名 String property = environment.getProperty("os.name"); // 包含 Windows 则说明是 windows 系统,返回true if (property.contains("Windows")) { return true; } return false; } }
postProcessBeanDefinitionRegistry 内核心方法
注解核心处理类:ConfigurationClassPostProcessor,执行流程如下:
BDRPP#postProcessBeanDefinitionRegistry 方法—>processConfigBeanDefinitions
BDRPP 接口下的 postProcessBeanDefinitionRegistry 方法会优先于 BFPP 接口下方法进行执行,此流程在介绍 refresh 方法时已经详细分析过了.
processConfigBeanDefinitions 前置准备工作
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { // 创建存放 BeanDefinitionHolder 对象集合 List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); // 当前 registry 就是 DefaultListableBeanFactory,获取所有已经注册的 BeanDefinition beanName String[] candidateNames = registry.getBeanDefinitionNames(); // 遍历所有要处理 beanDefinition 名称,筛选对应 beanDefinition(被注解修饰的) for (String beanName : candidateNames) { // 获取指定名称 BeanDefinition 对象 BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 如果 beanDefinition 中 configurationClass 属性不等于空,那么意味着已经处理过,输出日志信息 if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } // 判断当前 BeanDefinition 是否是一个配置类,并为 BeanDefinition 设置属性为 lite 或 full,此处设置属性值是为了后续进行调用 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 添加到对应的集合对象中 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // 如果没有发现任何配置类,则直接返回 if (configCandidates.isEmpty()) { return; } // 如果适用,则按照先前确定的@Order的值排序 configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // 判断当前类型是否是 SingletonBeanRegistry 类型 SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { // 类型的强制转换 sbr = (SingletonBeanRegistry) registry; // 判断是否有自定义 beanName 生成器 if (!this.localBeanNameGeneratorSet) { // 获取自定义 beanName 生成器 BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); // 如果有自定义的命名生成策略 if (generator != null) { // 设置组件扫描 beanName 生成策略 this.componentScanBeanNameGenerator = generator; // 设置 import bean name 生成策略 this.importBeanNameGenerator = generator; } } } // 如果环境对象等于空,那么就重新创建新的环境对象 if (this.environment == null) { this.environment = new StandardEnvironment(); } // 实例化 ConfigurationClassParser 类,并初始化相关的参数,完成配置类的解析工作 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 创建两个集合对象, // 存放相关的BeanDefinitionHolder对象 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // 存放扫描包下的所有bean Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { // 解析带有 @Controller、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean 的 BeanDefinition parser.parse(candidates); // 将解析完的 Configuration 配置类进行校验 // 1、配置类不能是 final // 2、@Bean 修饰的方法必须可以重写以支持CGLIB parser.validate(); // 获取所有的bean,包括扫描的bean对象,@Import导入的bean对象 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); // 清除掉已经解析处理过的配置类 configClasses.removeAll(alreadyParsed); // 判断读取器是否为空,如果为空的话,就创建完全填充好的 ConfigurationClass 实例的读取器 if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器 this.reader.loadBeanDefinitions(configClasses); // 添加到已经处理的集合中 alreadyParsed.addAll(configClasses); candidates.clear(); // 这里判断 registry.getBeanDefinitionCount() > candidateNames.length 目的是为了知道 reader.loadBeanDefinitions(configClasses) 这一步有没有向 BeanDefinitionMap 中添加新的BeanDefinition // 实际上就是看配置类(例如 AppConfig 类会向 BeanDefinitionMap 中添加 bean) // 如果有,registry.getBeanDefinitionCount() 就会大于 candidateNames.length // 这样就需要再次遍历新加入的 BeanDefinition,并判断这些 bean 是否已经被解析过了,如果未解析,需要重新进行解析 // 这里 AppConfig 类向容器中添加 bean,实际上在 parser.parse() 这一步已经全部被解析了 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()); } // 如果有未解析的类,则将其添加到 candidates 中,这样 candidates 不为空,就会进入到下一次的 while 的循环中 for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // 注册 ImportRegistry Bean 信息为了支持 ImportAware 配置类 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(); } }
该方法流程如下:
- 先遍历所有被自动加载的 BeanDefinition 信息,一般都是会把标注了 @Configuration 注解的类优先进行注入,然后判断当前 BD 属性
configurationClass
是否为空,若不为空,说明该类型已经被注入,否则就调用 Configuration 工具类检测类型是否标注了 @Configuration 注解,为其设置好相关的属性准备工作,该方法源码及注释如下: - 对配置类进行排序,根据 order 属性值 ,属性值越大的优先进行处理
- 判断是否有自定义的 beanName 生成器,若有进行实例化,否则不处理,一般无特殊的要求这里使用的就是 Spring 内部的生成器
- 若当前环境对象为空,就创建一个标准环境对象进行后续的值处理工作
- 实例化 ConfigurationClassParser 核心类,会调用其 parse 方法对配置类集合进行具体解析(parse->processConfigurationClass)
第一点描述的方法源码及注释如下:
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { // 获取bean定义信息中的class类名 String className = beanDef.getBeanClassName(); // 如果className为空,或者bean定义信息中的factoryMethod不等于空,那么直接返回 if (className == null || beanDef.getFactoryMethodName() != null) { return false; } AnnotationMetadata metadata; // 通过注解注入的 bd 都是 AnnotatedGenericBeanDefinition,实现了AnnotatedBeanDefinition // spring 内部 bd 是 RootBeanDefinition,实现了 AbstractBeanDefinition // 此处主要用于判断是否归属于 AnnotatedBeanDefinition if (beanDef instanceof AnnotatedBeanDefinition && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) { // 从当前bean的定义信息中获取元数据信息 metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata(); } // 判断是否是 spring 中默认的 BeanDefinition else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) { // 获取当前 bean 对象 Class 对象 Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass(); // 如果class实例是下面四种类型或接口的子类、父接口等任何一种情况,直接返回 if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) || BeanPostProcessor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass) || EventListenerFactory.class.isAssignableFrom(beanClass)) { return false; } // 为给定类创建新的AnnotationMetadata实例 metadata = AnnotationMetadata.introspect(beanClass); } // 如果上述两种情况都不符合 else { try { // 获取className的MetadataReader实例 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 定义元数据被 @Configuration 注解标注的属性字典值 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); // 如果 bean 被 @Configuration 注解标注,且属性 proxyBeanMethods 为 true(使用代理模式),则将 bean 定义为 full if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } // 如果 bean 被 @Configuration 注解标注,且被注解 @Component,@ComponentScan、@Import、@ImportResource 或者 @Bean 标记的方法,则将 bean 定义标记为 lite else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } // bean 定义是一个标记为 full 或 lite 候选项,有设置 order 则设置 order 属性值 Integer order = getOrder(metadata); // 如果值不为空的话,那么直接设置值到具体 beanDefinition if (order != null) { // 设置 bean 定义 order 值 beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; }
如果是 @Configuration 注解并且该注解属性 proxyBeanMethods 配置为 true默认值就是 true
,该属性的含义就是为了注明该类下的 @Bean 方法是否被动态代理拦截实现单例,则标识为 full
如果注解属性 proxyBeanMethods 为 false 并且加了 @Bean、@Component、@ComponentScan、@Import、@ImportResource 注解的,则标识为 lite
以上两者都不是的话则不进行处理,否则将其加入到配置类集合中进行后续方法的处理
ConfigurationClassParser#parse 方法
public void parse(Set<BeanDefinitionHolder> configCandidates) { // 循环遍历 configCandidates for (BeanDefinitionHolder holder : configCandidates) { // 获取 BeanDefinition BeanDefinition bd = holder.getBeanDefinition(); // 根据 BeanDefinition 类型的不同,调用 parse 不同的重载方法,实际上最终都是调用 processConfigurationClass 方法 try { // 注解类型 if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // 有class对象的 else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).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); } } // 执行找到 DeferredImportSelector,它是 ImportSelector 的一个子类 // ImportSelector 被设计成和 @Import 注解同样的效果,但是实现了 ImportSelector 类可以条件性的决定导入某些配置 // DeferredImportSelector 设计是在所有其他的配置类被处理后才进行处理,因为一个配置类它内部依赖于其他 bean,其他 bean 需要优先进行构建 this.deferredImportSelectorHandler.process(); }
parse 方法存在于多个重载方法,但最终调用的都是 processConfigurationClass
方法
// 根据注解元数据、beanName 解析配置文件,有注解元数据 protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); } // 根据 Class、beanName 解析配置文件,有 Class 对象 protected final void parse(Class<?> clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER); } // 根据 className、beanName 解析配置文件,读取元数据 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), DEFAULT_EXCLUSION_FILTER); }
区分三种不同的类型,优先级不同而已
- 存在于注解,一般在 SpringBoot 下定义的都是这种方式,只有当我们对 spring 进行扩展实现时注入业务相关的 bean,才会是如下两种类型
- 存在 Class、beanName,在注入 beanDefinition 时直接指定的就是
.Class
- 存在 className、beanName,在注入 beanDefinition 时指定的是类的全限定的名称
deferredImportSelectorHandler#process
方法内部的执行流程在该文章有介绍:
SpringBoot 自动装配流程及源码剖析
ConfigurationClassParser#doProcessConfigurationClass 方法
在实际处理配置类前,再作一些前置校验的工作,避免调用后续方法作无用的动作
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { // 判断是否跳过解析 if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // 第一次进入的时候,configurationClass size 为 0,existingClass 肯定为 null,在此处理 configuration 重复 import // 如果同一个配置类被处理两次,两次都属于被 import 则合并导入类返回,如果配置类不是被导入的,则移除旧的使用新的配置类 ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { // 如果要处理的配置类 configClass 在已经分析处理的配置类记录中已存在,合并两者的importBy属性 existingClass.mergeImportedBy(configClass); } // 否则忽略新导入的配置类,因为存在 non-imported 类重写它 return; } else { // 明确 bean 定义发现,可能取代了 import,允许移除老的已加载配置类 this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // 处理配置类,由于配置类可能存在父类(若父类的全类名是以java开头的,则除外),所有需要将 configClass变成 sourceClass 去解析,然后返回 sourceClass 父类 // 如果此时父类为空,则不会进行 while 循环去解析,如果父类不为空,则会循环的去解析父类 // SourceClass 意义:简单的包装类,目的是为了以统一的方式去处理带有注解的类,不管这些类是如何加载的 // 如果无法理解,可以把它当做一个黑盒,不会影响看 spring 源码主流程 SourceClass sourceClass = asSourceClass(configClass, filter); do { // 解析各种注解 sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); // 将解析的配置类存储起来,这样回到 parse 方法时,能取到值 this.configurationClasses.put(configClass, configClass); }
- 调用
shouldSkip
方法,基于 @Conditional 标签判断该对象是否要跳过,如果不满足 @Conditional 中 value 中的条件,就跳过该 Bean,不会注入容器 - 判断该配置类是否已经被处理过:如果被处理过,则判断当前类和已配置过的类是否都是被 import 导入的,是则对两者的
importedBy
属性进行合并,否则就先进行移除然后重新加入配置类 - 前期的校验和准备工作做完以后,再调用
doProcessConfigurationClass
进行具体的注解解析工作
由于 doProcessConfigurationClass 方法内部处理的流程较长,如下分为不同节点进行解析
processMemberClasses
递归处理内部类
// @Configuration 继承了 @Component if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // 递归处理内部类,因为内部类也是一个配置类,配置类上有 @configuration 注解,该注解继承 @Component,if 判断为 true,调用 processMemberClasses 方法,递归解析配置类中的内部类 processMemberClasses(configClass, sourceClass, filter); }
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,Predicate<String> filter) 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)) { // 出现配置类循环导入,则直接报错 this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { // 将配置类入栈 this.importStack.push(configClass); try { // 调用 processConfigurationClass 方法,因为内部类中还可能包含内部类,所以需要在做循环解析,实际工作中是不会有这中情况的 processConfigurationClass(candidate.asConfigClass(configClass), filter); } finally { // 解析完出栈 this.importStack.pop(); } } } } }
获取当前配置类的子类,对子类进行判别其是否属于配置类:@Component、@ComponentScan、@Import、@ImportResource,是就追加到 candidates
集合中,后面对其进行排序.
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // 不考虑接口 if (metadata.isInterface()) { return false; } // 检查是否被注解 @Component、@ComponentScan、@Import、@ImportResource 标注 for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 最后检查是否有 @Bean 标注的方法 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; } }
对 candidates
集合进行遍历,若当前导入栈已存在当前配置类,则直接抛出异常结束:配置类不可重复注入
;如果未存在,则调用 processConfigurationClass 方法继续往回走,因为内部类可能也会是一个配置类,所以调用该方法