前言
开心一刻
女儿: "妈妈, 你这么漂亮, 当年怎么嫁给了爸爸呢?"
妈妈: "当年你爸不是穷嘛!'
女儿: "穷你还嫁给他!"
妈妈: "那时候刚刚毕业参加工作, 领导对我说, 他是我的扶贫对象, 我年轻理解错了, 就嫁给他了!"
女儿......
@Import 注解应用
应用开发中, 当我们的功能模块比较多时, 往往会按模块或类别对 Spring 的 bean 配置文件进行管理, 使配置文件模块化, 更容易维护; spring3.0 之前, 对 Spring xml bean 文件进行拆分, 例如
- <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-boot-autoconfig, 四种都有配置, 不用 down 下来运行, 看一眼具体如何配置即可
运行测试用例, 结果如下
可以看到, Dog,Cat,Role,User,Permission 的实例都已经注册到了 spring 容器, 也就是说上述讲的 @Import 的 4 种方式都是能够将实例注册到 spring 容器的
@Import 注解原理
@Import 何以有如此强大的功能, 背后肯定有某个团队在运作, 而这个团队是谁了, 就是 spring;spring 容器肯定在某个阶段有对 @Import 进行了处理, 至于 spring 是在什么时候对 @Import 进行了怎样的处理, 我们来跟一跟源码; ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor, 那么它会在 spring 启动的 refresh 阶段被应用, 我们从 refresh 的 invokeBeanFactoryPostProcessors 方法开始
ConfigurationClassPostProcessor
注意此时 spring 容器中的 bean 定义与 bean 实例, 数量非常少, 大家可以留心观察下
一路跟下来, 我们来到 processConfigBeanDefinitions 方法, 该方法会创建一个 ConfigurationClassParser 对象, 该对象会分析所有 @Configuration 注解的配置类, 产生一组 ConfigurationClass 对象, 然后从这组 ConfigurationClass 对象中加载 bean 定义
ConfigurationClassParser
主要是 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();
- }
- View Code
从启动类 (示例中是 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;
- }
- View Code
上述代码中写了相关注释, 有兴趣的同学可以更进一步的去跟, 这里我只跟下 processImports 方法, 因为这个与自动配置息息相关
起始的 ConfigurationClass 包括: 1, 工程中所有我们自定义的被 @Configuration 修饰的类, 示例中就只有 AnimalConfig;2, 应用的启动类, 示例中是: AutoConfigApplication.
我们自定义的 ConfigurationClass 一般不会包含多级父级 ConfigurationClass, 例如 AnimalConfig, 就没有父级 ConfigurationClass, 解析就比较简单, 我们无需关注, 但 AutoConfigApplication 就不一样了, 他往往会被多个注解修饰, 而这些注解会牵扯出多个 ConfigurationClass, 需要递归处理所有的 ConfigurationClass; 上图中, 我们跟到了一个比较重要的类: AutoConfigurationImportSelector, 实例化之后封装成了 DeferredImportSelectorHolder 对象, 存放到了 ConfigurationClassParser 的 deferredImportSelectors 属性中
自动配置源码解析
有人可能有这样的疑问: 哪来的 AutoConfigurationImportSelector, 它有什么用? 客观莫急, 我们慢慢往下看
我们的应用启动类被 @SpringBootApplication, 它是个组合注解, 详情如下
相信大家都看到 @Import(AutoConfigurationImportSelector.class)了, ConfigurationClassParser 就是从此解析到的 AutoConfigurationImportSelector, 至于 AutoConfigurationImportSelector 有什么用, 马上揭晓; 我们回到 ConfigurationClassParser 的 parse 方法, 里面还有个很重要的方法: processDeferredImportSelectors, 值得我们详细跟下
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);
- }
- View Code
从类路径下的所有 spring.facoties 文件中读取 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有值, 此时获取的是全路径类名的数组, 然后进行筛选过滤, 1, 先去重处理, 因为多个 spring.factories 中可能存在重复的; 2, 然后剔除我们配置的需要排除的类, 包括 @SpringBootApplication 注解的 exclude,excludeName, 以及配置文件中的 spring.autoconfigure.exclude;3, 条件过滤, 过滤出满足自己条件注解的配置类. 最终获取所有满足条件的自动配置类, 示例中有 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 的自动配置源码解析就完成了, 有兴趣的可以更近一步的深入
总结
1, 各个方法之间的调用时序图如下, 结合这个时序图看上面的内容, 更好看懂
2,springboot 自动配置底层依赖的是 SpringFactoriesLoader 和 AutoConfigurationImportSelector;@EnableAutoConfiguration 注解就像一个八爪鱼, 抓取所有满足条件的配置类, 然后读取其中的 bean 定义到 spring 容器,@EnableAutoConfiguration 得以生效的关键组件关系图如下
来源: https://www.cnblogs.com/youzhibing/p/10559275.html