【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)(下)

postProcessBeanDefinitionRegistry()处理过程总结


1.使用工具ConfigurationClassParser尝试发现所有的配置(@Configuration)类

2.使用工具ConfigurationClassBeanDefinitionReader注册所发现的配置类中所有的bean定义

3.结束执行的条件是所有配置类都被发现和处理,相应的bean定义注册到容器(内部有递归)


ConfigurationClassPostProcessor#postProcessBeanFactory处理逻辑


这里就是增强配置类,添加一个 ImportAwareBeanPostProcessor,这个类用来处理 ImportAware 接口实现


  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);\
    // 这一步的意思是:如果processConfigBeanDefinitions没被执行过(不支持hook的时候不会执行)
    // 这里会补充去执行processConfigBeanDefinitions这个方法
    if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    // 这个是核心方法~~~~~~~~~~~~~~
    enhanceConfigurationClasses(beanFactory);
    // 添加一个后置处理器。ImportAwareBeanPostProcessor它是一个静态内部类
    // 记住:它实现了SmartInstantiationAwareBeanPostProcessor这个接口就可以了。后面会有作用的
    //InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命周期的另外两个时期提供扩展的回调接口
    //即实例化Bean之前(调用postProcessBeforeInstantiation方法)和实例化Bean之后(调用postProcessAfterInstantiation方法)
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }


enhanceConfigurationClasses如下:


  public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    // 拿到所有的Bean名称,当前环境一共9个(6个基础Bean+rootConfig+helloServiceImpl+parent视图类)
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      // 显然,能进来这个条件的,就只剩rootConfig这个类定义了
      // 从此处也可以看出,如果是lite版的@Configuration,是不会增强的
      if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
        if (!(beanDef instanceof AbstractBeanDefinition)) {
          throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
              beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
        }
        else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
          logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
              "' since its singleton instance has been created too early. The typical cause " +
              "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
              "return type: Consider declaring such methods as 'static'.");
        }
        configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
    }
    // 装载着等待被加强的一些配置类们
    if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
    }
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      try {
        // Set enhanced subclass of the user-specified bean class
        //  拿到原始的rootConfig类
        Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
        if (configClass != null) {
          // 对此类进行cglib增强
          // 注意增强后的类为:class com.fsx.config.RootConfig$$EnhancerBySpringCGLIB$$13993c97
          Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
          if (configClass != enhancedClass) {
            if (logger.isDebugEnabled()) {
              logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
            }
            // 把增强后的类配置类set进去。
            // 所以我们getBean()配置类,拿出来的是配置类的代理对象  是被CGLIB代理过的
            beanDef.setBeanClass(enhancedClass);
          }
        }
      } catch (Throwable ex) {
        throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
      }
    }
  }


可能有人会问,Spring为何要用cglib增强配置文件@Configuration呢?

其实上面提到的Full和Lite的区别已经能够有所答案了:

我们通过源码发现:只有full模式的才会去增强,然后增强带来的好处是:Spring可以更好的管理Bean的依赖关系了。比如@Bean之间方法之间的调用,我们发现,其实是去Spring容器里去找Bean了,而并不是再生成了一个实例。(它的缺点是使用了代理,带来的性能影响完全可以忽略)


其实这些可以通过我们自己书写代码来避免,但是Spring为了让他的自动化识别来得更加强大,所以采用代理技术来接管这些配置Bean的依赖,可谓对开发者十分的友好


上面源码也指出了:Liter模式是不会采用代理的,因此它的Bean依赖关系程序员自己去把控吧。建议:不要使用Lite模式,会带来不少莫名其妙的坑

@Configuration注解的配置类有如下要求

1.@Configuration不可以是final类型

2.@Configuration不可以是匿名类

3.嵌套的@Configuration必须是静态类


ConfigurationClassBeanDefinitionReader使用详解


功能:读取一组已经被完整解析的配置类ConfigurationClass,基于它们所携带的信息向给定bean容器BeanDefinitionRegistry注册其中所有的bean定义。


该内部工具如上,由BeanDefinitionRegistryPostProcessor来使用。Spring中的责任分工是非常明确的:


ConfigurationClassParser负责去找到所有的配置类。(包括做加强操作)

然后交给ConfigurationClassBeanDefinitionReader将这些配置类中的bean定义注册到容器

该类只提供了一个public方法供外面调用:


这个方法是根据传的配置们,去解析每个配置文件所标注的@Bean们,一起其余细节~

  public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    //TrackedConditionEvaluator是个内部类:是去解析@Conditional相关注解的。借助了conditionEvaluator去计算处理  主要是看看要不要shouldSkip()
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    // 遍历处理参数configurationModel中的每个配置类
    // 这里需要特别注意的是,此环境下,这里size不是1,而是2(rootConfig和helloServiceImpl)
    // 因为对于parser来说,只要是@Component都是一个组件(配置文件),只是是Lite模式而已
    // 因此我们也是可以在任意一个@Component标注的类上使用@Bean向里面注册Bean的,相当于采用的Lite模式。只是,只是我们一般不会去这么干而已,毕竟要职责单一
    for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
  }
  //从指定的一个配置类ConfigurationClass中提取bean定义信息并注册bean定义到bean容器 :
  //1. 配置类本身要注册为bean定义  2. 配置类中的@Bean注解方法要注册为配置类
  private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    // 判断是否需要跳过,与之前解析@Configuration判断是否跳过的逻辑是相同的 借助了conditionEvaluator。如果需要
    // 显然这里,哪怕是helloServiceImpl都不会被跳过
    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;
    }
    // 如果这个类是@Import进来的  那就注册为一个BeanDefinition   比如这种@Import(Child.class)  这里就会是true
    if (configClass.isImported()) {
      registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    // 这里处理的是所有标注有@Bean注解的方法们,然后注册成BeanDefinition 
    // 同时会解析一些列的@Bean内的属性,以及可以标注的其余注解  
    // 备注:方法访问权限无所谓,private都行。然后static的也行
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    //加载@ImportResource注解配置的资源需要生成的BeanDefinition
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 调用自定义的ImportBeanDefinitionRegistrar的registerBeanDefinitions方法注册BeanDefinition
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
  }

就这样通过这个Reader,把所有的Bean定义都加进容器了,后面就可以很方便的获取到了

ConfigurationClassParser 总结


Spring的工具类ConfigurationClassParser用于分析@Configuration注解的配置类,产生一组ConfigurationClass对象。


分析过程主要是递归分析配置类的注解@Import(比如我们的@EnableWebMvc注解,就@Import(DelegatingWebMvcConfiguration.class),然后它就是一个@Configuration)

`,配置类内部嵌套类,找出其中所有的配置类,然后返回这组配置类


该工具主要由ConfigurationClassPostProcessor使用,而ConfigurationClassPostProcessor是一个BeanDefinitionRegistryPostProcessor/BeanFactoryPostProcessor,它会在容器启动过程中,应用上下文上执行各个BeanFactoryPostProcessor时被执行。


ConfigurationClassParser 所在包 : org.springframework.context.annotation。由此可知,Spring给这个处理器的定位,就是去处理解析相关注解的


关于此Parserd的详细讲解,我找到了一篇讲得非常全面的文章供以参考,这里我就不再班门弄斧了:

Spring 工具类 ConfigurationClassParser 分析得到配置类

getImports、collectImports

这里面特别的说一下比较有意思的getImports()(collectImports):这个方法目的是递归去搜集到所有的@Import注解

  private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    // 装载所有的搜集到的import
    Set<SourceClass> imports = new LinkedHashSet<>();
    // 这个集合很有意思:就是去看看所有的内嵌类、以及注解是否有@Import注解
    // 比如看下面这个截图,会把所有的注解都给翻出来,哪怕是注解的注解
    Set<SourceClass> visited = new LinkedHashSet<>();
    collectImports(sourceClass, imports, visited);
    return imports;
  }
  private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
      throws IOException {
    // 此处什么时候返回true,什么时候返回false,请操作HashMap的put方法的返回值,看什么时候返回null
    // 答案:put一个新key,返回null。put一个已经存在的key,返回老的value值
    // 因此此处把add放在if条件里,是比较有技巧性的(若放置的是新的,返回null,若已经存在,就返回的false,不需要用contains()进一步判断了)
    if (visited.add(sourceClass)) {
      for (SourceClass annotation : sourceClass.getAnnotations()) {
        String annName = annotation.getMetadata().getClassName();
        // 此处不能以java打头,是为了过滤源注解:比如java.lang.annotation.Target这种
        // 并且这个注解如果已经是Import注解了,那也就停止递归了
        if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
          collectImports(annotation, imports, visited);
        }
      }
      imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
  }

image.png



我们可以看到在解析到@EnableWebMvc的时候,拿到了它的@Import,拿到DelegatingWebMvcConfiguration,但是我们发现它也还是个@Configuration

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ... }

需要注意的是:它的父类WebMvcConfigurationSupport,里面有非常多的@Bean注解的方法,比如RequestMappingHandlerMapping、BeanNameUrlHandlerMapping等等共18个类都会被注册到容器里(Spring非常强大,配置文件都会解析父类的@Bean标签),理解了这里,到时候后面讲解为何SpringBoot环境下,若我们写了@EnableWebMvc这个注解,就脱离Spring的管理了 就非常好理解其中的原因了~~~~~~~~~~~~~~


然后吧这些@Import交给processImports()去处理。进而又会递归式的处理@Configuration文件一样处理(内部也就可以写@Bean之类隐式的给容器注册Bean)。

image.png

总结


Spring设计了很多的后置处理器,让调用者可以在Bean定义时、Bean生成前后等等时机参与进来。而我们此处的ConfigurationClassPostProcessor就是Spring自己为我们实现的,来解析@Confiuration以及相关配置注解的处理器


了解了此处理器的解析过程,在我们自己去处理配置文件的时候,也能够更加的得心应手。比如知其然,知其所以然后,我们就能更加熟练的运用@Import、@ImportSelect这种高级注解实现特定的设计模式。这些在Spring Boot的整体框架设计中,得到了大量的运用


相关文章
|
2月前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
2月前
|
Java 对象存储 开发者
解析Spring Cloud与Netflix OSS:微服务架构中的左右手如何协同作战
Spring Cloud与Netflix OSS不仅是现代微服务架构中不可或缺的一部分,它们还通过不断的技术创新和社区贡献推动了整个行业的发展。无论是对于初创企业还是大型组织来说,掌握并合理运用这两套工具,都能极大地提升软件系统的灵活性、可扩展性以及整体性能。随着云计算和容器化技术的进一步普及,Spring Cloud与Netflix OSS将继续引领微服务技术的发展潮流。
61 0
|
29天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
人工智能 缓存 Java
深入解析Spring AI框架:在Java应用中实现智能化交互的关键
【10月更文挑战第12天】Spring AI 是 Spring 框架家族的新成员,旨在满足 Java 应用程序对人工智能集成的需求。它支持自然语言处理、图像识别等多种 AI 技术,并提供与云服务(如 OpenAI、Azure Cognitive Services)及本地模型的无缝集成。通过简单的配置和编码,开发者可轻松实现 AI 功能,同时应对模型切换、数据安全及性能优化等挑战。
113 3
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
130 9
|
1月前
|
存储 Java 程序员
SpringIOC和DI的代码实现,Spring如何存取对象?@Controller、@Service、@Repository、@Component、@Configuration、@Bean DI详解
本文详细讲解了Spring框架中IOC容器如何存储和取出Bean对象,包括五大类注解(@Controller、@Service、@Repository、@Component、@Configuration)和方法注解@Bean的用法,以及DI(依赖注入)的三种注入方式:属性注入、构造方法注入和Setter注入,并分析了它们的优缺点。
31 0
SpringIOC和DI的代码实现,Spring如何存取对象?@Controller、@Service、@Repository、@Component、@Configuration、@Bean DI详解
|
2月前
|
XML 监控 Java
Spring Cloud全解析:熔断之Hystrix简介
Hystrix 是由 Netflix 开源的延迟和容错库,用于提高分布式系统的弹性。它通过断路器模式、资源隔离、服务降级及限流等机制防止服务雪崩。Hystrix 基于命令模式,通过 `HystrixCommand` 封装对外部依赖的调用逻辑。断路器能在依赖服务故障时快速返回备选响应,避免长时间等待。此外,Hystrix 还提供了监控功能,能够实时监控运行指标和配置变化。依赖管理方面,可通过 `@EnableHystrix` 启用 Hystrix 支持,并配置全局或局部的降级策略。结合 Feign 可实现客户端的服务降级。
175 23
下一篇
无影云桌面