上一篇 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做了什么?

上源码:

  1. @Override
  2. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  3. int registryId = System.identityHashCode(registry);
  4. if (this.registriesPostProcessed.contains(registryId)) {
  5. throw new IllegalStateException(
  6. "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
  7. }
  8. if (this.factoriesPostProcessed.contains(registryId)) {
  9. throw new IllegalStateException(
  10. "postProcessBeanFactory already called on this post-processor against " + registry);
  11. }
  12. this.registriesPostProcessed.add(registryId);
  13. // 处理配置的BeanDefinition
  14. processConfigBeanDefinitions(registry);
  15. }

整个方法核心是执行了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注解,那么按照条件进行解析。

自定义自动装配

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

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

  1. @Override
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. if (!isEnabled(annotationMetadata)) {
  4. return NO_IMPORTS;
  5. }
  6. // 获取自动装配的实体
  7. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  8. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  9. }
  10. protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  11. if (!isEnabled(annotationMetadata)) {
  12. return EMPTY_ENTRY;
  13. }
  14. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  15. // 获取合格(候选)的配置
  16. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  17. configurations = removeDuplicates(configurations);
  18. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  19. checkExcludedClasses(configurations, exclusions);
  20. configurations.removeAll(exclusions);
  21. configurations = getConfigurationClassFilter().filter(configurations);
  22. fireAutoConfigurationImportEvents(configurations, exclusions);
  23. return new AutoConfigurationEntry(configurations, exclusions);
  24. }
  25. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  26. // 加载配置,根据factoryType,这里的FactoryType就是@EnableAutoConfiguration注解
  27. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
  28. getBeanClassLoader());
  29. Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
  30. + "are using a custom packaging, make sure that file is correct.");
  31. return configurations;
  32. }
  33. protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  34. // 直接返回@EnableAutoConfiguration 注解
  35. return EnableAutoConfiguration.class;
  36. }
  37. public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  38. String factoryTypeName = factoryType.getName();
  39. // 加载spring.factories文件并解析
  40. return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
  41. }
  42. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  43. MultiValueMap<String, String> result = cache.get(classLoader);
  44. if (result != null) {
  45. return result;
  46. }
  47. try
  48. // 这里获取的url就是:
  49. // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  50. Enumeration<URL> urls = (classLoader != null ?
  51. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  52. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  53. result = new LinkedMultiValueMap<>();
  54. while (urls.hasMoreElements()) {
  55. URL url = urls.nextElement();
  56. UrlResource resource = new UrlResource(url);
  57. // 读取属性文件,获取到key为EnableAutoConfiguration,value为需要加载的类
  58. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  59. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  60. String factoryTypeName = ((String) entry.getKey()).trim();
  61. for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  62. result.add(factoryTypeName, factoryImplementationName.trim());
  63. }
  64. }
  65. }
  66. cache.put(classLoader, result);
  67. return result;
  68. }
  69. catch (IOException ex) {
  70. throw new IllegalArgumentException("Unable to load factories from location [" +
  71. FACTORIES_RESOURCE_LOCATION + "]", ex);
  72. }
  73. }

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

定义自动装配注解

  1. /**
  2. * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
  3. * @since 1.0
  4. **/
  5. @Target(ElementType.TYPE)
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Import(MyImportSelector.class)
  8. public @interface EnableRedwinterAutoConfiguration {
  9. }

创建MyInportSelector类

  1. /**
  2. * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
  3. * @since 1.0
  4. **/
  5. public class MyImportSelector implements DeferredImportSelector {
  6. @Override
  7. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  8. ClassLoader classLoader = this.getClass().getClassLoader();
  9. // 加载需要装配的类
  10. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getFactoryTypeClass(), classLoader);
  11. return configurations.toArray(new String[configurations.size()]);
  12. }
  13. private Class<?> getFactoryTypeClass() {
  14. return EnableRedwinterAutoConfiguration.class;
  15. }
  16. }

创建启动类

  1. /**
  2. * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
  3. * @since 1.0
  4. **/
  5. @Configuration
  6. @EnableRedwinterAutoConfiguration
  7. public class RedwinterApplication {
  8. public static void main(String[] args) {
  9. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  10. context.scan("com.redwinter.test.config");
  11. context.refresh();
  12. }
  13. }

创建需要装配的类

  1. /**
  2. * @author <a href=""https://www.cnblogs.com/redwinter/">redwinter</a>
  3. * @since 1.0
  4. **/
  5. @Configuration
  6. public class MyConfiguration {
  7. @Bean
  8. @Conditional(RedwinterStrCondition.class)
  9. public String myStr() {
  10. return "redwinter";
  11. }
  12. public static class RedwinterStrCondition implements ConfigurationCondition {
  13. @Override
  14. public ConfigurationPhase getConfigurationPhase() {
  15. return ConfigurationPhase.REGISTER_BEAN;
  16. }
  17. @Override
  18. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  19. System.out.println("开始匹配。。。");
  20. return true;
  21. }
  22. }
  23. }

创建spring.factories文件

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

启动验证

debug断点:

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

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

Spring 源码(7)Spring的注解是如何解析的?的更多相关文章

  1. Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器

    本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...

  2. Spring源码情操陶冶-AnnotationConfigBeanDefinitionParser注解配置解析器

    本文承接前文Spring源码情操陶冶-自定义节点的解析,分析spring中的context:annotation-config节点如何被解析 源码概览 对BeanDefinitionParser接口的 ...

  3. Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?

    阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...

  4. Spring源码解读Spring IOC原理

    一.什么是Ioc/DI? IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等. 先从我们自己设计这样一个视角来考虑: 所谓控制反转,就是把原先我们代码里面需要实现的对象创建.依赖的代码,反 ...

  5. 初探Spring源码之Spring Bean的生命周期

    写在前面的话: 学无止境,写博客纯粹是一种乐趣而已,把自己理解的东西分享出去,不意味全是对的,欢迎指正! Spring 容器初始化过程做了什么? AnnotationConfigApplication ...

  6. Spring源码阅读-spring启动

    web.xml web.xml中的spring容器配置 <listener> <listener-class>org.springframework.web.context.C ...

  7. spring源码系列(一)sring源码编译 spring源码下载 spring源码阅读

    想对spring框架进行深入的学习一下,看看源代码,提升和沉淀下自己,工欲善其事必先利其器,还是先搭建环境吧. 环境搭建 sping源码之前是svn管理,现在已经迁移到了github中了,新版本基于g ...

  8. Spring源码:Spring IoC容器加载过程(2)

    Spring源码版本:4.3.23.RELEASE 一.加载XML配置 通过XML配置创建Spring,创建入口是使用org.springframework.context.support.Class ...

  9. Spring源码:Spring IoC容器加载过程(1)

    Spring源码版本:4.3.23.RELEASE 一.加载过程概览 Spring容器加载过程可以在org.springframework.context.support.AbstractApplic ...

  10. 【Spring 源码】Spring 加载资源并装配对象的过程(XmlBeanDefinitionReader)

    Spring 加载资源并装配对象过程 在Spring中对XML配置文件的解析从3.1版本开始不再推荐使用XmlBeanFactory而是使用XmlBeanDefinitionReader. Class ...

随机推荐

  1. lombok的@builder 不能新建DO对象 Lombok存在的一些问题

    1. 实体类加上 lombok的@builder之后  就不能新建对象了,,,构造函数被覆盖了? 加上两个标签之后解决 2.Lombok存在的一些问题 lombok问题 @Builder和@NoArg ...

  2. 你了解过Servlet3.0吗?

    Servlet3.0相对于Servlet2.0来说最大的改变是引入了Annotation标注来取代xml配置,用于简化web应用的开发和部署.最主要几项特性: 1. 新增的注解支持:该版本新增了若干注 ...

  3. 为什么Java不支持运算符重载?

    另一个类似棘手的Java问题.为什么 C++ 支持运算符重载而 Java 不支持? 有人可能会说+运算符在 Java 中已被重载用于字符串连接,不要被这些论据所欺骗.与 C++ 不同,Java 不支持 ...

  4. MyISAM 表格将在哪里存储,并且还提供其存储格式?

    每个 MyISAM 表格以三种格式存储在磁盘上: ·".frm"文件存储表定义 ·数据文件具有".MYD"(MYData)扩展名 索引文件具有".MY ...

  5. Redis 是单进程单线程的?

    Redis 是单进程单线程的,redis 利用队列技术将并发访问变为串行访问,消 除了传统数据库串行控制的开销.

  6. java-注解相关

    注解 概念:说明程序的,给计算机看  注释:用文字描述程序 先了解一些怎么正常javadoc文档 1:给类或者方法添加doc注释 2:通过命令javadoc 执行 类.java文件 新建的类: /** ...

  7. Leetcode刷题之链表增加头结点的前缀节点

    链表之增加头结点的前缀节点 在许多链表题中往往需要在题目给的头结点之前增加一个前缀节点 通常在删除链表和头结点需要交换时需要用到这一操作 因为增加这个节点就避免了对删除头结点这种特殊情况的特殊处理 而 ...

  8. 序列化多表操作、请求与响应、视图组件(子类与拓展类)、继承GenericAPIView类重写接口

    今日内容概要 序列化多表操作 请求与相应 视图组件 内容详细 1.序列化多表操作 模型类 models.py中 # 新建django项目 # 创建表 模型类models.py中: from djang ...

  9. 无单位数字和行高 —— 别说你懂CSS相对单位

    前段时间试译了Keith J.Grant的CSS好书<CSS in Depth>,其中的第二章<Working with relative units>,书中对relative ...

  10. 开源HTML5游戏引擎Kiwi.js 1.0正式发布

    Kiwi.js是由GameLab开发的一款全新的开源HTML5 JavaScript游戏引擎.在经过一年多的开发和测试之后,终于在日前正式发布了Kiwi.js 1.0版本. 其创始人Dan Milwa ...