Spring 框架中的 @Enable* 注解是怎样实现的?

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 概述Spring 的项目中,我们经常会使用 @Enable 开头的注解到配置类中,添加了这种注解之后,便会开启一些功能特性。常用的注解如 @EnableWebMvc、@EnableTransactionManagement、@EnableAsync、@EnableScheduling 等等。

概述


Spring 的项目中,我们经常会使用 @Enable 开头的注解到配置类中,添加了这种注解之后,便会开启一些功能特性。常用的注解如 @EnableWebMvc、@EnableTransactionManagement、@EnableAsync、@EnableScheduling 等等。集成到 Spring 的第三方的框架中,我们也经常会看到这样的注解,如阿里巴巴开源的缓存框架 jetcache,便需要使用注解 @EnableCreateCacheAnnotation 开启缓存能力。本篇将尝试对其实现进行分析,并实现一个自己的 @Enable* 注解。


@Enable* 注解实现分析


以 @EnableScheduling 注解为例,其源码如下。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}


除了最常用的 Java 提供的元注解,这个 @EnableScheduling 使用了 @Import 注解,很明显 @Enable* 就是利用 @Import 注解实现的。查看 @Import 源码如下。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
  /**
   * {@link Configuration @Configuration}, {@link ImportSelector},
   * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
   */
  Class<?>[] value();
}


@Import 可以标注在类型上,只有一个类型为 Class 的 value 属性,根据注释我们可以看到,value 属性值可以是 @Configuration 配置类、ImportSelector、ImportBeanDefinitionRegistrar 或者常规组件类。Spring 读取到这几个类型时的处理如下。


@Configuration 配置类:Spring 对配置类进行解析。

ImportSelector:实例化该接口实现类,提供元数据给接口中的方法,将返回的类型作为配置类进行解析。

ImportBeanDefinitionRegistrar:实例化该类型,提供元数据给接口中的方法,由用户自己控制注入哪些 bean。

常规类:将该类作为 bean 进行注入。

value 值为配置类的时候 Spring 会对配置类进行解析,值为常规组件类的时候 Spring 会把组件类作为 Spring 的 bean 进行注入,这两个相对比较容易理解。下面对剩下的两个类型进行分析。


ImportSelector 定义

ImportSelector 接口定义如下。


public interface ImportSelector {
  /**
   * 选择并返回哪些类应该被导入,导入的类将作为配置类再次进行解析
   * AnnotationMetadata 是 @Configuration 配置类的元数据
   */
  String[] selectImports(AnnotationMetadata importingClassMetadata);
  /**
   * 排除导入的类
   * @since 5.2.4
   */
  @Nullable
  default Predicate<String> getExclusionFilter() {
    return null;
  }
}


ImportSelector#selectImports 方法中的参数 AnnotationMetadata 是使用 @Import 注解的配置类的元数据,返回值将作为配置类再次进行解析。如果返回值是实现 BeanFactoryPostProcessor 或者 BeanPostProcessor 接口的组件,我们将有机会在 Spring 应用上下文或 bean 的生命周期中做一些其他的操作。如果返回值是配置类,我们还能根据 @Conditional 注解根据条件注入一些 bean 。@Conditional 注解的使用见 Spring 条件注解 @Conditional 使用及其底层实现 。


ImportSelector#getExclusionFilter 方法则用于从导入的类中排除我们不想导入的类。


ImportBeanDefinitionRegistrar 定义

ImportBeanDefinitionRegistrar 定义如下。


public interface ImportBeanDefinitionRegistrar {
  // 注册 BeanDefinition
  default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
      BeanNameGenerator importBeanNameGenerator) {
    registerBeanDefinitions(importingClassMetadata, registry);
  }
  // 注册 BeanDefinition
  default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  }
}


相比 ImportSelector 只能获取配置类的元数据,ImportBeanDefinitionRegistrar 接口提供了更为灵活的方法,还能收到参数 BeanDefinitionRegistry、BeanNameGenerator 。 BeanDefinitionRegistry 可以方便我们获取已注册的 BeanDefinition,注册自己定义的 BeanDefinition。BeanNameGenerator 则用于生成 bean 的名称。


@Import 实现分析


通过上面的描述,我们知道 @Enable* 注解是通过 @Import 实现的,而 @Import 的处理在 Spring 中的配置类解析过程中。解析 @Import 注解的核心代码位置为 ConfigurationClassParser#processImports,源码如下。


  /**
   * 处理 @Import 注解
   *
   * @param configClass             标注或元标注 @Import 的配置类
   * @param currentSourceClass      配置类对应的 SourceClass
   * @param importCandidates        @Import 注解 value 属性值集合或 ImportSelector.selectImports 返回值
   * @param exclusionFilter         不要处理配置类的过滤器
   * @param checkForCircularImports 是否检查循环 @Import
   */
  private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {
    if (importCandidates.isEmpty()) {
      return;
    }
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    } else {
      this.importStack.push(configClass);
      try {
        for (SourceClass candidate : importCandidates) {
          // @Import value 属性值是 ImportSelector.class
          if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            ImportSelector selector = ParserStrategyUtils
                .instantiateClass(candidateClass, ImportSelector.class,
                    this.environment, this.resourceLoader, this.registry);
            Predicate<String> selectorFilter = selector.getExclusionFilter();
            if (selectorFilter != null) {
              exclusionFilter = exclusionFilter.or(selectorFilter);
            }
            if (selector instanceof DeferredImportSelector) {
              // @Import value 属性值是 DeferredImportSelector.class
              this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            } else {
              String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames,
                  exclusionFilter);
              // 递归处理 ImportSelector 返回的配置类
              processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter,
                  false);
            }
          } 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 =
                ParserStrategyUtils
                    .instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                        this.environment, this.resourceLoader, this.registry);
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
          } else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            this.importStack.registerImport(
                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
          }
        }
      } catch (BeanDefinitionStoreException ex) {
        throw ex;
      } catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
                configClass.getMetadata().getClassName() + "]", ex);
      } finally {
        this.importStack.pop();
      }
    }
  }


processImports 方法整体结构较为清晰,对 @Import 注解的 value 属性值进行循环处理。


首先判断是否为 ImportSelector 类型,如果是则 调用 ImportSelector#selectImports 方法,并将取到的类型作为配置类,进行递归解析 @Import。

否则如果类型为 ImportBeanDefinitionRegistrar,则对其实例化,并添加到 importBeanDefinitionRegistrars 中,后面将统一进行处理。

否则将导入的类作为 bean 进行注册,然后作为配置类递归进行处理。


实现自己的 @Enable* 注解


下面尝试实现一个 @EnableHelloWorld ,配置类上使用了该注解后将自动注入一个 HelloWorld 的 bean。代码如下。


public class HelloWorld {
    public void sayHello(){
        System.out.println("hello,world");
    }
}
public class HelloWorldSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{HelloWorld.class.getName()};
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HelloWorldSelector.class)
public @interface EnableHelloWorld {
}
@EnableHelloWorld
@Configuration
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("com.zzuhkp");
        context.refresh();
        context.getBean(HelloWorld.class).sayHello();
    }
}


程序执行后成功打印出“hello,world”,表明自定义 @Enable* 成功生效。


总结


@Enable* 注解通过 @Import 注解实现。@Import 注解在配置类解析过程中进行处理。通过 @Import 可以方便的注入自定义的 bean 。

目录
相关文章
|
14天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
35 0
|
26天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
38 0
|
21天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
43 4
SpringBoot必须掌握的常用注解!
|
30天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
48 1
Spring 框架:Java 开发者的春天
|
23天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
40 2
|
22天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
80 1
|
23天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
76 2
|
23天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
34 1
下一篇
无影云桌面