开心一刻 html
女儿: “妈妈,你这么漂亮,当年怎么嫁给了爸爸呢?”
妈妈: “当年你爸不是穷嘛!‘
女儿: “穷你还嫁给他!”
妈妈: “那时候刚刚毕业参加工做,领导对我说,他是个人扶贫对象,我年轻理解错了,就嫁给他了!”
女儿......java
应用开发中,当咱们的功能模块比较多时,每每会按模块或类别对Spring的bean配置文件进行管理,使配置文件模块化,更容易维护;spring3.0以前,对Spring XML bean文件进行拆分, 例如git
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <import resource="config/user.xml"/> <import resource="config/role.xml"/> <import resource="config/permission.xml"/> </beans>
spring3.0及以后,引入了@Import注解,提供与Spring XML中的<import />元素等效的功能;spring4.2以前,@Import只支持导入配置类(@Configuration修饰的类、ImportSelector实现类和ImportBeanDefinitionRegistrar实现类),而spring4.2及以后不只支持导入配置类,同时也支持导入常规的java类(如普通的User类)spring
示例地址:spring-boot-autoconfig,四种都有配置,不用down下来运行,看一眼具体如何配置便可数组
运行测试用例,结果以下springboot
能够看到,Dog、Cat、Role、User、Permission的实例都已经注册到了spring容器,也就是说上述讲的@Import的4种方式都是可以将实例注册到spring容器的ide
@Import何以有如此强大的功能,背后确定有某个团队在运做,而这个团队是谁了,就是spring;spring容器确定在某个阶段有对@Import进行了处理,至于spring是在何时对@Import进行了怎样的处理,咱们来跟一跟源码;ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,那么它会在spring启动的refresh阶段被应用,咱们从refresh的invokeBeanFactoryPostProcessors方法开始模块化
注意此时spring容器中的bean定义与bean实例,数量很是少,你们能够留心观察下spring-boot
一路跟下来,咱们来到processConfigBeanDefinitions方法,该方法会建立一个ConfigurationClassParser对象,该对象会分析全部@Configuration注解的配置类,产生一组ConfigurationClass对象,而后从这组ConfigurationClass对象中加载bean定义测试
主要是parse方法
public void parse(Set<BeanDefinitionHolder> configCandidates) { this.deferredImportSelectors = new LinkedList<>(); // 一般状况下configCandidates中就一个BeanDefinitionHolder,关联的是咱们的启动类 // 示例中是:com.lee.autoconfig.AutoConfigApplication for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 被@Configuration注解修饰的类会被解析为AnnotatedGenericBeanDefinition,AnnotatedGenericBeanDefinition实现类AnnotatedBeanDefinition接口 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); } } // 处理延迟的ImportSelector,这里本文的重点:自动配置的入口 processDeferredImportSelectors(); }
从启动类(示例中是com.lee.autoconfig.AutoConfigApplication)开始,递归解析配置类以及配置类的父级配置类;边跟边注意beanFactory中beanDefinitionMap的变化,ConfigurationClassParser对象有beanFactory的引用,属性名叫registry;咱们能够仔细看下doProcessConfigurationClass方法
/** * 经过从源类中读取注解、成员和方法来构建一个完整的配置类:ConfigurationClass * 注意返回值,是父级类或null(null包含两种状况,没找到父级类或以前已经处理完成) */ @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // 递归处理配置类内置的成员类 processMemberClasses(configClass, sourceClass); // 处理配置类上全部@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.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // 处理配置类上全部的@ComponentScan注解,包括@ComponentScans和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) { // 当即扫描@ComponentScan修饰的配置类, // 一般是从启动类所在的包(示例中是com.lee.autoconfig)开始扫描,扫描配置类(被@Configuration修饰的类) Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // 进一步检查经过配置类扫描获得的bean定义集,并在须要时递归解析 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()); } } } } // 处理配置类上全部的@Import注解 // 包括@Import支持的4种类型:ImportSelector、ImportBeanDefinitionRegistrar、@Configuration和普通java类 // 普通java类会被按@Configuration方式处理 processImports(configClass, sourceClass, getImports(sourceClass), true); // 处理配置类上全部的@ImportResource注解,xml方式的bean就是其中之一 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); } } // 处理配置类中被@Bean修饰的方法 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // 处理默认的方法或接口 processInterfaces(configClass, sourceClass); // 处理父级类,若是有的话 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; }
上述代码中写了相关注释,有兴趣的同窗能够更进一步的去跟,这里我只跟下processImports方法,由于这个与自动配置息息相关
起始的ConfigurationClass包括:一、工程中全部咱们自定义的被@Configuration修饰的类,示例中就只有AnimalConfig;二、应用的启动类,示例中是:AutoConfigApplication。
咱们自定义的ConfigurationClass通常不会包含多级父级ConfigurationClass,例如AnimalConfig,就没有父级ConfigurationClass,解析就比较简单,咱们无需关注,但AutoConfigApplication就不同了,他每每会被多个注解修饰,而这些注解会牵扯出多个ConfigurationClass,须要递归处理全部的ConfigurationClass;上图中,咱们跟到了一个比较重要的类:AutoConfigurationImportSelector,实例化以后封装成了DeferredImportSelectorHolder对象,存放到了ConfigurationClassParser的deferredImportSelectors属性中
有人可能有这样的疑问:哪来的AutoConfigurationImportSelector,它有什么用? 客观莫急,咱们慢慢往下看
咱们的应用启动类被@SpringBootApplication,它是个组合注解,详情以下
相信你们都看到@Import(AutoConfigurationImportSelector.class)了,ConfigurationClassParser就是今后解析到的AutoConfigurationImportSelector,至于AutoConfigurationImportSelector有什么用,立刻揭晓;咱们回到ConfigurationClassParser的parse方法,里面还有个很重要的方法:processDeferredImportSelectors,值得咱们详细跟下
说的简单点,从类路径下的全部spring.facoties文件中读取所有的自动配置类(spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的值),而后筛选出知足条件的配置类,封装成ConfigurationClass,存放到ConfigurationClassParser的configurationClasses属性中
说的详细点,分两个方法进行说明
selectImports方法
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); // 从类路径下的spring.factories文件中读取全部配置类(org.springframework.boot.autoconfigure.EnableAutoConfigurationd的值) // 获得全部配置类的全路径类名的集合 - 数组 // 此时获得的是类名,至于该类存不存在,还须要在下面步骤中进行检验 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重重复的 configurations = removeDuplicates(configurations); // 获取须要排除的配置类,@SpringBootApplication exclude和excludeName的值 // 以及配置文件中spring.autoconfigure.exclude的值 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 验证排除的配置类是否存在 - 类路径下是否存在该类 checkExcludedClasses(configurations, exclusions); // 剔除须要排除的配置类 configurations.removeAll(exclusions); // 进行过滤 - 经过配置类的条件注解(@ConditionalOnClass、@ConditionalOnBean等)来判断配置类是否符合条件 configurations = filter(configurations, autoConfigurationMetadata); // 触发自动配置事件 - ConditionEvaluationReportAutoConfigurationImportListener fireAutoConfigurationImportEvents(configurations, exclusions); // 返回@Import方式 全部知足条件的配置类 return StringUtils.toStringArray(configurations); }
从类路径下的全部spring.facoties文件中读取org.springframework.boot.autoconfigure.EnableAutoConfiguration的全部值,此时获取的是全路径类名的数组,而后进行筛选过滤,一、先去重处理,由于多个spring.factories中可能存在重复的;二、而后剔除咱们配置的须要排除的类,包括@SpringBootApplication注解的exclude、excludeName,以及配置文件中的spring.autoconfigure.exclude;三、条件过滤,过滤出知足本身条件注解的配置类。最终获取全部知足条件的自动配置类,示例中有24个。
条件注解更详细的信息请查看:spring-boot-2.0.3源码篇 - @Configuration、Condition与@Conditional,读取spring.facoties文件的详细信息请查看:spring-boot-2.0.3启动源码篇一 - SpringApplication构造方法
processImports方法
这个方法在解析ConfigurationClassParser的parse方法的时候已经用到过了,只是没有作说明,它其实就是用来处理配置类上的@Import注解的;上述selectImports方法解析出来的配置类,每一个配置类都会通过processImports方法处理,递归处理@Import注解,就与递归处理咱们的启动类的@Import注解同样,从而获取全部的自动配置类;springboot的自动配置就是这样实现的。
此时还只是获取了知足条件的自动配置类,配置类中的bean定义加载尚未进行,咱们回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,其中有以下代码
// 各类方式的配置类的解析,包括springboot的自动配置 - @Import、AutoConfigurationImportSelector parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); // 将配置类中的bean定义加载到beanFactory
至此,springboot的自动配置源码解析就完成了,有兴趣的能够更近一步的深刻
一、各个方法之间的调用时序图以下,结合这个时序图看上面的内容,更好看懂
二、springboot自动配置底层依赖的是SpringFactoriesLoader和AutoConfigurationImportSelector;@EnableAutoConfiguration注解就像一个八爪鱼,抓取全部知足条件的配置类,而后读取其中的bean定义到spring容器,@EnableAutoConfiguration得以生效的关键组件关系图以下