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

 更新时间:2021年09月28日 10:29:01   作者:一撸向北  
这篇文章主要介绍了spring boot@EnableXXXX注解编程模型,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

@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)

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

相关文章

  • maven依赖关系中的<scope>provided</scope>使用详解

    maven依赖关系中的<scope>provided</scope>使用详解

    这篇文章主要介绍了maven依赖关系中的<scope>provided</scope>使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java Socket实现UDP编程浅析

    Java Socket实现UDP编程浅析

    类 DatagramSocket 何 DatagramPacket(数据包/数据报) 实现了基于 UDP协议网络程序;UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP数据报一定能够安全送达目的地,也不确定什么时候可以抵达
    2022-11-11
  • SSM框架中测试单元的使用 spring整合Junit过程详解

    SSM框架中测试单元的使用 spring整合Junit过程详解

    这篇文章主要介绍了SSM框架中测试单元的使用 spring整合Junit过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • springboot 错误处理小结

    springboot 错误处理小结

    在 java web开发过程中,难免会有一些系统异常或人为产生一些异常。在 RESTful springboot 项目中如何优雅的处理?下面脚本之家小编给大家带来了springboot 错误处理小结,感兴趣的朋友一起看看吧
    2018-03-03
  • Springboot+redis+Interceptor+自定义annotation实现接口自动幂等

    Springboot+redis+Interceptor+自定义annotation实现接口自动幂等

    本篇文章给大家介绍了使用springboot和拦截器、redis来优雅的实现接口幂等,对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响数据一次是非常重要的,感兴趣的朋友跟随小编一起看看吧
    2019-07-07
  • redis统计APP在线人数的实例

    redis统计APP在线人数的实例

    这篇文章主要介绍了redis统计APP在线人数的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Java多线程三种主要实现方式解析

    Java多线程三种主要实现方式解析

    这篇文章主要介绍了Java多线程三种主要实现方式解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • SpringBoot整合消息队列RabbitMQ

    SpringBoot整合消息队列RabbitMQ

    SpringBoot整合RabbitMQ很容易,但是整合的目的是为了使用,那要使用RabbitMQ就要对其有一定的了解,不然容易整成一团浆糊。因为说到底,SpringBoot只是在封装RabbitMQ的API,让其更容易使用而已,废话不多说,让我们一起整它
    2023-03-03
  • Java 详解包装类Integer与int有哪些共通和不同

    Java 详解包装类Integer与int有哪些共通和不同

    这篇文章主要介绍的是 Java中int和Integer的区别,Java 是一种强数据类型的语言,因此所有的属性必须有一个数据类型,下面文章基于Java详细int和Integer有何区别,需要的朋友可以参考一下
    2022-04-04
  • java控制Pdf自动打印的小例子

    java控制Pdf自动打印的小例子

    java控制Pdf自动打印的小例子,需要的朋友可以参考一下
    2013-04-04

最新评论