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

简介: 【小家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的整体框架设计中,得到了大量的运用


相关文章
|
6月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1448 1
|
3月前
|
Java 测试技术 数据库
说一说 SpringBoot 整合 Junit5 常用注解
我是小假 期待与你的下一次相遇 ~
|
6月前
|
JSON 前端开发 Java
Spring MVC常用的注解
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中 的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 @Controller:控制器的注解,表示是表现层,不能用用别的注解代替 @RestController : 组合注解 @Conntroller + @ResponseBody @GetMapping , @PostMapping , @Put
|
6月前
|
Java Spring
Spring Boot的核心注解是哪个?他由哪几个注解组成的?
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 : ● @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能; ● @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项 ● @ComponentScan:Spring组件扫描
|
6月前
|
Java 测试技术 Spring
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
167 3
|
5月前
|
人工智能 缓存 自然语言处理
保姆级Spring AI 注解式开发教程,你肯定想不到还能这么玩!
这是一份详尽的 Spring AI 注解式开发教程,涵盖从环境配置到高级功能的全流程。Spring AI 是 Spring 框架中的一个模块,支持 NLP、CV 等 AI 任务。通过注解(如自定义 `@AiPrompt`)与 AOP 切面技术,简化了 AI 服务集成,实现业务逻辑与 AI 基础设施解耦。教程包含创建项目、配置文件、流式响应处理、缓存优化及多任务并行执行等内容,助你快速构建高效、可维护的 AI 应用。
|
7月前
|
监控 Java Spring
SpringBoot:SpringBoot通过注解监测Controller接口
本文详细介绍了如何通过Spring Boot注解监测Controller接口,包括自定义注解、AOP切面的创建和使用以及具体的示例代码。通过这种方式,可以方便地在Controller方法执行前后添加日志记录、性能监控和异常处理逻辑,而无需修改方法本身的代码。这种方法不仅提高了代码的可维护性,还增强了系统的监控能力。希望本文能帮助您更好地理解和应用Spring Boot中的注解监测技术。
235 16
|
6月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
491 0
|
7月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
327 0
|
6月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
590 29

推荐镜像

更多
  • DNS