Spring 源码(7)Spring的注解是如何解析的?

上一篇 https://www.cnblogs.com/redwinter/p/16196359.html 介绍了 BeanFactoryPostProcessor的执行过程,这篇文章介绍 Spring中配置的注解是如何通过 ConfigurationClassPostProcessor解析的,另外分析下 Spring Boot自动装配是如何处理的。

ConfigurationClassPostProcessor 解析了哪些注解?

在上一篇文章https://www.cnblogs.com/redwinter/p/16196359.html 我们知道 ConfigurationClassPostProcessor实际上是 BeanFactoryPostProcessor的一个实现类,他特殊的地方是他还实现了 BeanDefinitionRegisterPostProcessor接口,所以 ConfigurationClassPostProcessor 既要实现 BeanFactoryPostProcessor的接口方法 postProcessBeanFactory也要实现 BeanDefinitionRegisterPostProcessor的接口方法 postProcessBeanDefinitionRegistry,并且在解析的时候先执行了 postProcessBeanDefinitionRegistry方法,再执行了 postProcessBeanDefinitionRegistry方法。

接下来我们看看 postProcessBeanDefinitionRegistry做了什么?

上源码:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  int registryId = System.identityHashCode(registry);
  if (this.registriesPostProcessed.contains(registryId)) {
    throw new IllegalStateException(
      "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
  }
  if (this.factoriesPostProcessed.contains(registryId)) {
    throw new IllegalStateException(
      "postProcessBeanFactory already called on this post-processor against " + registry);
  }
  this.registriesPostProcessed.add(registryId);
  // 处理配置的BeanDefinition
  processConfigBeanDefinitions(registry);
}

整个方法核心是执行了 processConfigBeanDefinitions方法,这个方法非常的长并且逻辑也复杂,代码我就不贴了,说一下大概的流程(较详细):

  • 先进行合格的 beanDefinition的检查
  • 获取到注解的元数据信息
  • 判断是包含 @Configuration注解,包含则合格,否则判断是否包含了 @Component@ComponentScan@Import@ImportResource注解,包含则合格,如果都不包含则不合格
  • 对合格的 BeanDefinition排序
  • 创建一个解析 @Configuration注解的解析器
  • 对合格的 BeanDefinition集合进行解析
  • 循环解析,最终调用 processConfigurationClass方法
  • 判断是否跳过解析,比如配置了 @Conditional注解的
  • 调用 doProcessConfigurationClass方法开始解析(下面的解析中可能会存在递归调用)
    • 解析 @Component注解
    • 判断是否包含内部类标记了 @Component,比如在标有 @Component注解的类里面创建一个内部类也标记了 @Component注解,如果有就会进行递归调用 processConfigurationClass方法
    • 解析 @PropertySources@PropertySource注解
    • 比如标记 @PropertySource("classpath:jdbc.properties"),这样就会把这个属性的值全部解析到环境信息的 propertySources属性中
    • 解析 @ComponetScans@ComponentScan注解
    • 比如配置了扫描的包,那么就会扫描出合格的 BeanDefinition,然后递归解析
    • 解析 @Import注解( Spring Boot自动装配的实现)
    • 递归解析出标记了 @Import注解的类放在 imports属性中
    • 解析 ImportSelector接口的实现类
    • 调用 ImportSelector#selectImports方法解析需要注册的类
    • 递归调用 processImports方法,然后将需要注册的类注册到 importBeanDefinitionRegistrars(这里会在后面进行 loadBeanDefinition
    • 解析 @ImportResource注解
    • 比如解析配置的 Springxml配置文件,最终放到 importedResources属性中(后面会进行 loadBeanDefinition
    • 解析 @Bean注解
    • 比如解析当前类标记了 @Bean的方法
    • 然后放在 beanMethods属性中(后面会进行 loadBeanDefinition
  • 加载 BeanDefinition从上面解析出来的类中
    • 循环遍历加载 BeanDefinition
    • 判断是否跳过,比如实现了 Condition接口的类
    • 加载标有 @BeanBeanDefinition
    • 加载从 ImportResource中解析的 BeanDefinition
    • 加载从 ImportSelector中配置的解析的 BeanDefinition

整个过程非常复杂,而且存在递归操作,读者可以按照我写的步骤进行 debug调试,当然可能会出现到处跳转不知所措的情况,多调几遍就好了,只要知道大致的流程,应该还是不难的。

总的来说就是解析了这些注解: @Component@PropertySource@PropertySources@ComponentScan@ComponentScans@Import@ImportResource@Bean,然后将标有这些注解的解析成 BeanDefinition,如果加上了 @Conditionnal注解,那么按照条件进行解析。

Spring 源码(7)Spring的注解是如何解析的?

自定义自动装配

现在开发都是用 SpringBoot,原因在于他非常的方便,引入即可使用,那么他是做到的呢?众所周知 Spring Boot有几个注解非常重要,比如: @SpringBootApplication@EnableAutoConfiguration@SpringBootConfiguration,其中最重要的是 @EnableAutoConfiguration,这个注解里面标记了 @Import(AutoConfigurationImportSelector.class),当然还标记了其他的,我们现在只关心这个 @Import,里面放入了一个 AutoConfigurationImportSelector类。

AutoConfigurationImportSelector类实现了 DeferredImportSelector接口,这个 DeferredImportSelector接口是 ImportSelector的子接口,表示延迟导入的意思。在上面的分析中,其实最主要的是实现他的接口 selectImports,直接源码:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  // 获取自动装配的实体
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  // 获取合格(候选)的配置
  List configurations = getCandidateConfigurations(annotationMetadata, attributes);
  configurations = removeDuplicates(configurations);
  Set exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  configurations = getConfigurationClassFilter().filter(configurations);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  // 加载配置,根据factoryType,这里的FactoryType就是@EnableAutoConfiguration注解
  List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                       getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                  + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

protected Class getSpringFactoriesLoaderFactoryClass() {
  // 直接返回@EnableAutoConfiguration 注解
  return EnableAutoConfiguration.class;
}

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
  String factoryTypeName = factoryType.getName();
  // 加载spring.factories文件并解析
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
  MultiValueMap result = cache.get(classLoader);
  if (result != null) {
    return result;
  }

  try
    // 这里获取的url就是:
    // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    Enumeration urls = (classLoader != null ?
                             classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                             ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    result = new LinkedMultiValueMap<>();
    while (urls.hasMoreElements()) {
      URL url = urls.nextElement();
      UrlResource resource = new UrlResource(url);
      // 读取属性文件,获取到key为EnableAutoConfiguration,value为需要加载的类
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}

所以我们也可以自己写一个进行自动装配,接下来实现一个简单的自动装配。

定义自动装配注解

/**
 * @author redwinter
 * @since 1.0
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface EnableRedwinterAutoConfiguration {
}

创建MyInportSelector类

/**
 * @author redwinter
 * @since 1.0
 **/
public class MyImportSelector implements DeferredImportSelector {
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    ClassLoader classLoader = this.getClass().getClassLoader();
    // 加载需要装配的类
    List configurations = SpringFactoriesLoader.loadFactoryNames(getFactoryTypeClass(), classLoader);
    return configurations.toArray(new String[configurations.size()]);
  }

  private Class getFactoryTypeClass() {
    return EnableRedwinterAutoConfiguration.class;
  }

}

创建启动类

/**
 * @author redwinter
 * @since 1.0
 **/
@Configuration
@EnableRedwinterAutoConfiguration
public class RedwinterApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.redwinter.test.config");
        context.refresh();
    }
}

创建需要装配的类

/**
 * @author redwinter
 * @since 1.0
 **/
@Configuration
public class MyConfiguration {

    @Bean
    @Conditional(RedwinterStrCondition.class)
    public String myStr() {
        return "redwinter";
    }

    public static class RedwinterStrCondition implements ConfigurationCondition {

        @Override
        public ConfigurationPhase getConfigurationPhase() {
            return ConfigurationPhase.REGISTER_BEAN;
        }

        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            System.out.println("开始匹配。。。");
            return true;
        }
    }

}

创建spring.factories文件

com.redwinter.test.config.EnableRedwinterAutoConfiguration=\
  com.redwinter.test.config.MyConfiguration

启动验证

debug断点:

Spring 源码(7)Spring的注解是如何解析的?

这就是 Spring Boot自动装配的简化版,总得来说我们完成了 SpringBeanFactoryPostProcessor的执行过程的解析,包括 Spring是如何进行注解解析的,其实就是 Spring在对 BeanDefinition在正式初始化为 Bean的前置处理,所以我们可以这个阶段进行很多扩展,比如占位符的处理 PropertySourcesPlaceholderConfigurer等。

接下来接续解读 AbstractApplicationContext#refresh方法对 BeanPostProcessor的注册。

Original: https://www.cnblogs.com/redwinter/p/16198942.html
Author: 玲丶蹊
Title: Spring 源码(7)Spring的注解是如何解析的?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/610064/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Vue中生成UUID

    {{elId}} shuaxun import {v4} from ‘uuid’ // npm install -S uuid export default { data () {…

    Java 2023年6月8日
    0102
  • SpringMVC笔记

    SpringMVC 一、SpringMVC介绍 1.什么是MVC 是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的JavaBean,作用…

    Java 2023年6月16日
    092
  • 分布式基础- 拜占庭将军问题

    一 背景 拜占庭将军问题是如何通过通讯方式来达成共识得问题,Leslie Lamport 来借助这个问题说明如何在分布式环境下达成共识。 拜占庭将军问题是这样的:拜占庭帝国的军队在…

    Java 2023年5月30日
    091
  • 2021/2/1

    系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何…

    Java 2023年6月5日
    071
  • 设置 java -jar 的进程显示名称

    我们经常会在Linux服务器上部署我们的java程序,总会使用 的命令在后台运行程序,这样运行后带来的问题就是,当我们想看相应的java进程时,使用jps命令,java进程只会显示…

    Java 2023年6月16日
    065
  • 写了个简洁的Typora+Markdown简历模板

    项目地址:https://github.com/CodingDocs/typora-markdown-resume (欢迎小伙伴们使用!个人能力有限,也欢迎小伙伴们一起完善这个简历…

    Java 2023年6月9日
    078
  • 11.Feign面试点及实战优化

    Feign之HTTP性能优化 Feign默认使用的JDK自带的HTTP方式 Feign最大的优化点是更换HTTP底层实现 目前Apache HTTPClient是一个非常好的选择 …

    Java 2023年6月8日
    067
  • mybatis

    posted @2019-02-24 16:55 Jessica程序猿 阅读(418 ) 评论() 编辑 Original: https://www.cnblogs.com/wuc…

    Java 2023年5月30日
    095
  • 记一次线上MySQL数据库死锁问题

    最近线上项目报了一个MySQL死锁(DealLock)错误,虽说对业务上是没有什么影响的,由于自己对数据库锁这块了解不是很多,之前也没怎么的在线上碰到过。这次刚好遇到了,便在此记录…

    Java 2023年6月7日
    071
  • 存储优化(2)-排序引起的慢查询优化

    排序引起的慢查询,通常不是那么容易发现,经常和数据分布有关系。往往在业务刚开始时并没有什么问题,但是随着业务的发展,数据分布呈现一种特定的规律,导致了慢查询,或者并不是什么慢查询,…

    Java 2023年6月8日
    093
  • 实验设计

    统计学是什么? 统计学是对令人困惑费解的问题作出 数字设想的艺术 一、对照实验 该部分的第一个例子由脊髓灰质炎的疫苗引入了 随机对照双盲实验。 其所总结出的实验设计的原则: 减小混…

    Java 2023年6月7日
    079
  • [SPLUSH WAVE] DragonMahjongg3~天空編~ 1.07版修改器+界面汉化

    修改器只针对游戏1.07版本,请勿升级游戏版本!! 修改器界面,上面的好理解,最下面的当你抓到第一张牌后会根据你抓的牌来给你一个和的牌,抓牌了后要等一秒才生效. 其他没啥说的了,修…

    Java 2023年5月29日
    064
  • Java 并发编程学习总结

    什么是并发编程,简单来说就是为了充分利用cpu,多个任务同时执行,快速完成任务。 并发编程相关的概念和技术看上非常零散,相关度也很低,想要学习好并发编程,可以从下面两方面入手:一是…

    Java 2023年5月29日
    065
  • Effective Java 第三版—— 87. 考虑使用自定义序列化形式

    Tips书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code注意,书中的有些代码里方法是基于Java 9…

    Java 2023年5月29日
    066
  • 用过Redis吗,它使用在哪些地方(使用场景)?

    1. 用过Redis吗,它使用在哪些地方(使用场景)? Redis是一种内存型数据库,用作数据库,缓存和消息代理。数据结构简单,读写速度快(毫秒级),原子操作。适用于: 数据高并发…

    Java 2023年6月15日
    077
  • spring boot实现超轻量级网关(反向代理、转发)

    在我们的rest服务中,需要暴露一个中间件的接口给用户,但是需要经过rest服务的认证,这是典型的网关使用场景。可以引入网关组件来搞定,但是引入zuul等中间件会增加系统复杂性,这…

    Java 2023年5月30日
    088
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球