spring boot@EnableXXXX注解编程模型讲解

目录
  • @EnableXXXX编程模型
  • @Import注解处理时机节点(@Confguration注解的类处理)
    • ImportSelector
    • ImportBeanDefinitionRegistrar处理
    • @Configurtion注解的类处理
    • 统一调用配置类解析出来的信息注册BeanDefinition

@EnableXXXX编程模型

在spring boot中,@EnableXXX注解的功能通常是开启某一种功能。根据某些外部配置自动装配一些bean,来达到开启某些功能的目的。光说很抽象,要具体分析。

@Enable模型的实现方式基本有3种。一个基本的@Enable注解的模型如下。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(XXXX.class)
public @interface EnableDiscoveryClient {
   /**
    * If true, the ServiceRegistry will automatically register the local server.
    */
   boolean autoRegister() default true;
}

对应XXXX.class的不同,有3种实现方式。

  • 普通配置类,里面包含@Bean方法用于实例化bean
  • ImportSelector实现类
  • ImportBeanDefinitionRegistrar实现类

上面3种类都属于@Import注解的导入对象,整个外部化配置过程围绕@Import注解进行解析,导入类。

@Import注解处理时机节点(@Confguration注解的类处理)

@Import注解的处理过程大致可以描述为:

  • 寻找BeanFactory中所有被@Configuration注解修饰的类,包括被@Configuration派生注解修饰的类。
  • 寻找被@Configuration注解修饰的类上的所有注解元信息(这里的搜索不仅是搜索当前注解,还会迭代往修饰注解的注解的注解上层…..一直搜索@Import,直到注解最原始的注解),获取@Import注解的导入类信息,如果没有则不处理。
  • 根据导入类的信息,判定为

普通配置类,里面包含@Bean方法用于实例化bean

ImportSelector实现类

ImportBeanDefinitionRegistrar实现类

3种形式进行处理。

从context启动开始跟踪主线处理代码,调用链条如下。

org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions(主线代码,必看)
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  //定义@Conguration注解修饰的类注册信息列表
 List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
 String[] candidateNames = registry.getBeanDefinitionNames();
//检查当前context中所有的bean注册信息
 for (String beanName : candidateNames) {
    BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
          ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
       if (logger.isDebugEnabled()) {
          logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
       }
    }
     //检查class是否是@Conguration注解修饰的类,包括被“继承”@Conguration注解的注解,例如@SpringBootConguration,具体可以跟踪ConfigurationClassUtils.checkConfigurationClassCandidate实现
    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
       configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    }
 }
 // Return immediately if no @Configuration classes were found
 if (configCandidates.isEmpty()) {
    return;
 }
 // Sort by previously determined @Order value, if applicable
  //对配置类排序,顺序由Ordered接口决定
 configCandidates.sort((bd1, bd2) -> {
    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
    return Integer.compare(i1, i2);
 });
//......略略略
 // Parse each @Configuration class
  //处理每一个配置类
 ConfigurationClassParser parser = new ConfigurationClassParser(
       this.metadataReaderFactory, this.problemReporter, this.environment,
       this.resourceLoader, this.componentScanBeanNameGenerator, registry);
 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
 do {
     //解析处理配置类逻辑
    parser.parse(candidates);
     //......略略略
 }
 while (!candidates.isEmpty());
//......略略略
}
org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set

ImportSelector

String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());

返回结果是所有需要导入的的类的全限定名。

对于全限定名数组,逐个进行org.springframework.context.annotation.ConfigurationClassParser#processImports,相当于循环调用processImports,把新导入的类也当做@Import导入的类处理,如果新导入的类继续导入新的类,就继续org.springframework.context.annotation.ConfigurationClassParser#processImports。直到新导入的类不是

ImportSelector。

ImportBeanDefinitionRegistrar处理

当@Import的类是不是ImportSelector之后,如果是ImportBeanDefinitionRegistrar,那就做BeanDefinition信息注册到BeanFactory操作,具体实现在org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions实现,在这里的处理过程是。

else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
   // Candidate class is an ImportBeanDefinitionRegistrar ->
   // delegate to it to register additional bean definitions
   Class<?> candidateClass = candidate.loadClass();
   ImportBeanDefinitionRegistrar registrar =
         BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
   ParserStrategyUtils.invokeAwareMethods(
         registrar, this.environment, this.resourceLoader, this.registry);
         //将ImportBeanDefinitionRegistrar放入map缓存起来
   configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
   this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}

先缓存@Import导入的ImportBeanDefinitionRegistrar信息,稍后统一调用ImportBeanDefinitionRegistrar加载注册BeanDefinition信息。

@Configurtion注解的类处理

重复上面的整个流程,处理这个被@Configuration注解标注的类。比较需要注意的是一般@Configuration注解标注的类常用@Bean方式来实例化实例。这里#Bean也会解析出一个BeanMethod信息集合,稍后跟ImportBeanDefinitionRegistrar的缓存信息一样统一调用然后注册BeanDefinition。

// Process individual @Bean methods
//对配置类的@Bean方法处理逻辑
//获取所有@Bean标注的方法元信息,后续处理
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
   configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

统一调用配置类解析出来的信息注册BeanDefinition

在解析完配置类之后,实际还没有进行BeanDefinition的注册,只得到了可以用来注册BeanDefinition的“信息工具”,利用@Bean得到了BeanMethod,@Import(xxxImportBeanDefinitionRegistrar.class)得到了ImportBeanDefinitionRegistrar的实现类。最终要使用这些工具进行BeanDefinition 信息注册。

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions中,当处理完@Configuration注解的类之后就进行ImportBeanDefinitionRegistrar的BeanDefinition注册加载。

//处理@Configuration,递归寻找@Configuration,以及解析@Configuration里面的@Import、@Bean、@Component、@ImportResource等。
parser.parse(candidates);
parser.validate();
//获取parser中解析得到的所有配置类
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());
}
//根据递归找出的配置类和解析配置类得到的信息,加载BeanDefinition
this.reader.loadBeanDefinitions(configClasses);
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
   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;
   }
   if (configClass.isImported()) {
      registerBeanDefinitionForImportedConfigurationClass(configClass);
   }
   for (BeanMethod beanMethod : configClass.getBeanMethods()) {
       //利用@Bean的Method加载BeanDefinition
      loadBeanDefinitionsForBeanMethod(beanMethod);
   }
   loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    //利用缓存的ImportBeanDefinitionRegistrar加载注册beandefintion
   loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars(以ImportBeanDefinitionRegistrar为例跟踪)
org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(注册BeanDefinition信息到BeanFactory)

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Spring Boot最核心的27个注解,你了解多少?

    导读  Spring Boot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较火热的微服务框架SpringCloud集成. 而Spring Boot之所以能够轻松地实现应用的创建及与其他框架快速集成,最核心的原因就在于它极大地简化了项目的配置,最大化地实现了"约定大于配置"的原则.然而基于Spring Boot虽然极大地方便了开发,但是也很容易让人"云里雾里",特别是各种

  • 解析SpringBoot @EnableAutoConfiguration的使用

    刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"}).当时觉得挺urgly的,但也没有去研究有没有更好的方式. 直到接触Spring Boot 后,发现其可以自动引入二方包的bean.不过一直没有看这块的实现原理.直到最近面试的时候被问到.所以就看了

  • Springboot基于enable模块驱动的实现

    enable作为模块驱动在Spring Farmework.Spring Boot.Spring Cloud使用,都是通过注解的形式以@enable作为前缀,一些常用注解如 框架 注解 模块 Spring Framework @EnableWebMvc Web MVC模块 Spring Framework @EnableTransactionmanagement Web MVC模块 Spring Framework @EnableCacheing Cacheing模块 Spring Framew

  • springBoot @Enable* 注解的使用

    1.为什么使用@SpringBootApplication注解,即可做到自动配置? 答:@SpringBootApplication,内部起作用的注解其实有3个.@EnableAutoConfiguration,@ComponentScan,@Configuration.这篇文章主要是讲解@EnableXX注解 2.为什么使用了@EnableAutoConfiguration.当使用了@ConfigurationProperties时,即可自动导入.yml 或者.properties里面的配置项

  • spring boot@EnableXXXX注解编程模型讲解

    目录 @EnableXXXX编程模型 @Import注解处理时机节点(@Confguration注解的类处理) ImportSelector ImportBeanDefinitionRegistrar处理 @Configurtion注解的类处理 统一调用配置类解析出来的信息注册BeanDefinition @EnableXXXX编程模型 在spring boot中,@EnableXXX注解的功能通常是开启某一种功能.根据某些外部配置自动装配一些bean,来达到开启某些功能的目的.光说很抽象,要具

  • Spring 注解编程模型相关知识详解

    Spring 中有一个概念叫「元注解」(Meta-Annotation),通过元注解,实现注解的「派生性」,官方的说法是「Annotation Hierarchy」. 什么是元注解 所谓元注解,即标注在注解上的注解.这种方式所形成的注解层级结构中,元注解在层级结构的上面,我叫它父注解(Super Annotation), 被注解的注解在层级结构的下面,叫它子注解(Sub Annotation).引入元注解的目的是为了实现属性重写(Attribute Override) 的目的. 举个简单的例子:

  • Spring Boot 通过注解实现数据校验的方法

    一.依赖 <!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> &l

  • spring boot基于注解的声明式事务配置详解

    事务配置 1.配置方式一 1)开启spring事务管理,在spring boot启动类添加注解@EnableTransactionManagement(proxyTargetClass = true):等同于xml配置方式的 <tx:annotation-driven />(注意:1项目中只需配置一次,2需要配置proxyTargetClass = true) 2)在项目中需要添加事务的类或方法上添加注解@Transactional(建议添加在方法上),一般使用默认属性即可,若要使用事务各属性

  • Spring Boot 条件注解详情

    目录 一 @Conditional扩展注解 1.1 Bean作为条件 1.1.1 @ConditionalOnBean 1.1.2 @ConditionalOnMissingBean 1.1.3 @ConditionalOnSingleCandidate 1.2 类作为条件 1.2.1 @ConditionalOnClass 1.2.2 @ConditionalOnMissingClass 1.3 SpEL表达式作为条件 1.4 JAVA版本作为判断条件 1.5 配置属性作为判断条件 1.6 资

  • Spring Boot 利用注解方式整合 MyBatis

    目录 前言 整合过程 新建 Spring Boot 项目 添加 pom 依赖 准备数据库 pojo 层 dao 层 service 层 controller 层 入口程序配置 网页测试 总结 前言 目前而言,国内大家使用最多的持久层框架可能还是 MyBatis 吧,那既然如此,更强大的 Spring Boot 遇上炽手可热的 MyBatis,又会擦出什么样的火花呢? 那本文就来看看,如何利用 SpringBoot 来整合 Mybatis. 如下图是总结的整合过程的大概流程,那接下来我们就来开始具

  • 详解Spring/Spring boot异步任务编程WebAsyncTask

    今天一起学习下如何在Spring中进行异步编程.我们都知道,web服务器处理请求 request 的线程是从线程池中获取的,这也不难解释,因为当web请求并发数非常大时,如何一个请求进来就创建一条处理线程,由于创建线程和线程上下文切换的开销是比较大的,web服务器最终将面临崩溃.另外,web服务器创建的处理线程从头到尾默认是同步执行的,也就是说,假如处理线程A负责处理请求B,那么当B没有 return 之前,处理线程A是不可以脱身去处理别的请求的,这将极大限制了web服务器的并发处理能力. 因此

  • Spring Boot 基于注解的 Redis 缓存使用详解

    看文本之前,请先确定你看过上一篇文章<Spring Boot Redis 集成配置>并保证 Redis 集成后正常可用,因为本文是基于上文继续增加的代码. 一.创建 Caching 配置类 RedisKeys.Java package com.shanhy.example.redis; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import org.springf

  • 基于Spring boot @Value 注解注入属性值的操作方法

    本文主要介绍Spring @Value 注解注入属性值的使用方法的分析,文章通过示例代码非常详细地介绍,对于每个人的学习或工作都有一定的参考学习价值 在使用spring框架的项目中,@Value是经常使用的注解之一.其功能是将与配置文件中的键对应的值分配给其带注解的属性.在日常使用中,我们常用的功能相对简单.本文使您系统地了解@Value的用法. @Value注入形式 根据注入的内容来源,@ Value属性注入功能可以分为两种:通过配置文件进行属性注入和通过非配置文件进行属性注入. 非配置文件注

  • Spring Boot @Conditional注解用法示例介绍

    引用Spring官方文档的说法介绍一下@Conditional注解:Spring5.0.15版本@Conditional注解官方文档 @Conditional表示仅当所有指定条件都匹配时,组件才有资格注册 . 该@Conditional注释可以在以下任一方式使用: 作为任何@Bean方法的方法级注释 作为任何类的直接或间接注释的类型级别注释 @Component,包括@Configuration类 作为元注释,目的是组成自定义构造型注释 改注解主要源码之一,通过match匹配,符合条件才装载到S

随机推荐