判断是配置类了,将配置类放入到configCandidates这个BeanDefinitionHolder的集合中存储,进行下一步的操作
ConfigurationClassParser#parse
public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 我们使用的注解驱动,所以会到这个parse进来处理。其实内部调用都是processConfigurationClass进行解析的 if (bd instanceof AnnotatedBeanDefinition) { //单反有注解标注的,都会走这里来解析 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } 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接口的类,最最后哦~~ processDeferredImportSelectors(); }
public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<>(); for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 我们使用的注解驱动,所以会到这个parse进来处理。其实内部调用都是processConfigurationClass进行解析的 if (bd instanceof AnnotatedBeanDefinition) { //单反有注解标注的,都会走这里来解析 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } 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接口的类,最最后哦~~ processDeferredImportSelectors(); }
该方法做了三件事如下:
1.实例化deferredImportSelectors
2.遍历configCandidates ,进行处理.根据BeanDefinition 的类型 做不同的处理,一般都会调用ConfigurationClassParser#parse 进行解析
3.处理ImportSelect
下面看看处理的核心方法processConfigurationClass:
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { //ConfigurationCondition继承自Condition接口 // ConfigurationPhase枚举类型的作用:ConfigurationPhase的作用就是根据条件来判断是否加载这个配置类 // 两个值:PARSE_CONFIGURATION 若条件不匹配就不加载此@Configuration // REGISTER_BEAN:无论如何,所有@Configurations都将被解析。 if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // 如果这个配置类已经存在了,后面又被@Import进来了~~~会走这里 然后做属性合并~ 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); } } // Recursively process the configuration class and its superclass hierarchy. // 请注意此处:while递归,只要方法不返回null,就会一直do下去~~~~~~~~ SourceClass sourceClass = asSourceClass(configClass); do { // doProcessConfigurationClassz这个方法是解析配置文件的核心方法,此处不做详细分析 sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); // 保存我们所有的配置类 注意:它是一个LinkedHashMap,所以是有序的 这点还比较重要~~~~和bean定义信息息息相关 this.configurationClasses.put(configClass, configClass); } // 解析@Configuration配置文件,然后加载进Bean的定义信息们 // 这个方法非常的重要,可以看到它加载Bean定义信息的一个顺序~~~~ @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 先去看看内部类 这个if判断是Spring5.x加上去的,这个我认为还是很有必要的。 // 因为@Import、@ImportResource这种属于lite模式的配置类,但是我们却不让他支持内部类了 if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first // 基本逻辑:内部类也可以有多个(支持lite模式和full模式,也支持order排序) // 若不是被import过的,那就顺便直接解析它(processConfigurationClass()) // 另外:该内部class可以是private 也可以是static~~~(建议用private) // 所以可以看到,把@Bean等定义在内部类里面,是有助于提升Bean的优先级的~~~~~ processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations 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 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 //getImports方法的实现 很有意思 processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations 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 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces 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; }
有关this.conditionEvaluator.shouldSkip()实际判断逻辑大概为:和@Conditional注解以及TypeFilter相关,具体处理逻辑这里省略
ConfigurationClass代表一个配置类,它内部维护了一些已经解析好的但是还没有被加入进Bean定义信息的原始信息,有必要做如下解释:
// @since 3.0 它就是普通的类,基本只有get set方法 final class ConfigurationClass { private final AnnotationMetadata metadata; private final Resource resource; @Nullable private String beanName; private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1); // 存储该配置类里所有标注@Bean注解的方法~~~~ private final Set<BeanMethod> beanMethods = new LinkedHashSet<>(); // 用Map保存着@ImportResource 导入进来的资源们~ private final Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>(); // 用Map保存着@Import中实现了`ImportBeanDefinitionRegistrar`接口的内容~ private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>(); final Set<String> skippedBeanMethods = new HashSet<>(); }
最终我归纳出一个扫描Bean的顺序(注意并不是Bean定义真正注册的顺序),解析@Configuration配置文件的顺序:
1.内部配置类:–> 它里面还可以有普通配置类一模一样的功能,但优先级最高,最终会放在configurationClasses这个map的第一位
2.@PropertySource:这个和Bean定义没啥关系了,属于Spring配置PropertySource的范畴。这个属性优先级相对较低
3.@ComponentScan:注意,注意,注意重说三。 这里扫描到的Bean定义,就直接register注册了,直接注册了,注解注册了。所以它的时机是非常早的。(另外:如果注册进去的Bean定义信息如果还是配置类,这里会继续parse(),所以最终能被扫描到的组件,最终都会当作一个配置类来处理,所以最终都会放进configurationClasses这个Map里面去)
4.@Import:相对复杂点,如下:
1.若就是一个普通类(标注@Configuration与否都无所谓反正会当作一个配置类来处理,也会放进configurationClasses缓存进去)
2.实现了ImportSelector:递归最红都成为第一步的类。若实现的是DeferredImportSelector接口,它会放在deferredImportSelectors属性里先保存着,等着外部的所有的configCandidates配置类全部解析完成后,统一processDeferredImportSelectors()。它的处理方式一样的,最终也是转为第一步的类
3.实现了ImportBeanDefinitionRegistrar:放置ConfigurationClass.importBeanDefinitionRegistrars属性里保存着
5.@ImportResource:一般用来导入xml文件。它是先放在ConfigurationClass.importedResources属性里放着
6.@Bean:找到所有@Bean的方法,然后保存到ConfigurationClass.beanMethods属性里
7.processInterfaces:处理该类实现的接口们的default方法(标注@Bean的有效)
8.处理父类:拿到父类,每个父类都是一个配置文件来处理(比如要有任何注解)。备注:!superclass.startsWith("java")全类名不以java打头,且没有被处理过(因为一个父类可议N个子类,但只能被处理一次)
9.return null:若全部处理完成后就返回null,停止递归。
由上可见,这九步中,唯独只有@ComponentScan扫描到的Bean这个时候的Bean定义信息是已经注册上去了的,其余的都还没有真正注册到注册中心。
注意:bean的注册的先后顺序,将直接影响到Bean的覆盖(默认就行Map,后注册的肯定先注册的,当然还和scope有关)
关于相同BeanName的覆盖问题,参考:【小家Spring】聊聊Spring的bean覆盖(同名beanName/beanId问题),介绍Spring名称生成策略接口BeanNameGenerator
其余的请继续往下看:
Bean定义信息的注册顺序:
由上面步骤可知,已经解析好的所有配置类(包含内部类、扫描到的组件等等)都已经全部放进了本类的configurationClasses这个属性Map里面。因此只需要知道它在什么时候被解析的就可以知道顺序了。
解析代码:ConfigurationClassPostProcessor#processConfigBeanDefinitions方法里:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ... do { parser.parse(candidates); parser.validate(); ... // 此处就拿出了我们已经处理好的所有配置类们(该配置文件下的所有组件们~~~~) Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); ... // reader:ConfigurationClassBeanDefinitionReader最终真正实现的Bean的注册 // 关于它的原始代码 下面有着重分析,因此此处只说一个结论,顺序 this.reader.loadBeanDefinitions(configClasses); }
此处结合我的一个案例说明,此时configClasses值如下:
而此时Bean注册中心里已经存在的Bean定义信息如下图:(我们发现只有Scan的才真正进入注册中心,其余比如@Import的都还不在里面~)
this.reader.loadBeanDefinitions(configClasses)
开始处理,参阅ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
方法:
class ConfigurationClassBeanDefinitionReader { // 对每个@Configuration 类文件做遍历(所以 Config配置文件的顺序还是挺重要的) public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } } // private 方法来解析每一个已经解析好的@Configuration配置文件~~~ private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { 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; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); } }
1.最先处理注册@Import进来的Bean定义~,判断依据是:configClass.isImported()。官方解释为:Return whether this configuration class was registered via @{@link Import} or automatically registered due to being nested within another configuration class 这句话的意思是说@Import或者内部类或者通过别的配置类放进来的都是被导入进来的~~~~
2.第二步开始注册@Bean进来的:若是static方法,beanDef.setBeanClassName(configClass.getMetadata().getClassName()) + beanDef.setFactoryMethodName(methodName);若是实例方法:beanDef.setFactoryBeanName(configClass.getBeanName())+ beanDef.setUniqueFactoryMethodName(methodName) 总之对使用者来说 没有太大的区别
3.注册importedResources进来的bean们。就是@ImportResource这里来的Bean定义
4.执行ImportBeanDefinitionRegistrar#registerBeanDefinitions()注册Bean定义信息~(也就是此处执行ImportBeanDefinitionRegistrar的接口方法)
this.reader.loadBeanDefinitions(configClasses)执行完成后,拥有的bean定义截图如下:(导入的最终也都作为独立的Bean注册进来了~)