前言

在 Spring 框架中有很多实用的功能,不需要写大量的配置代码,只需添加几个注解即可开启。 其中一个重要原因是那些 @EnableXXX 注解,它可以让你通过在配置类加上简单的注解来快速地开启诸如事务管理(@EnableTransactionManagement)、Spring MVC(@EnableWebMvc)或定时任务(@EnableScheduling)等功能。这些看起来简单的注解语句提供了很多功能,但它们的内部机制从表面上看却不太明显。 一方面,对于使用者来说用这么少的代码获得这么多实用的功能是很好的,但另一方面,如果你不了解某个东西的内部是如何工作的,就会使调试和解决问题更加困难。

设计目标

Spring 框架中那些 @EnableXXX 注解的设计目标是允许用户用最少的代码来开启复杂使用的功能。 此外,用户必须能够使用简单的默认值,或者允许手动配置该代码。最后,代码的复杂性要向框架使用者隐藏掉。 简而言之,让使用者设置大量的 Bean,并选择性地配置它们,而不必知道这些 Bean 的细节(或真正被设置的内容)。下面来看看具体的几个例子:

@EnableScheduling (导入一个 @Configuration 类)

首先要知道的是,@EnableXXX 注解并不神奇。实际上在 BeanFactory 中并不知道这些注解的具体内容,而且在 BeanFactory 类中,核心功能和特定注解(如 @EnableWebMvc)或它们所存放的 jar 包(如 spring-web)之间没有任何依赖关系。 让我们看一下 @EnableScheduling,下面看看它是如何工作的。 定义一个 SchedulingConfig 配置类,如下所示:

@Configuration
@EnableScheduling
public class SchedulingConfig {
// some bean in here
}

上面的内容没有什么特别之处。只是一个用 @EnableScheduling 注释的标准 Java 配置。@EnableScheduling 让你以设定的频率执行某些方法。例如,你可以每 10 分钟运行 BankService.transferMoneyToMghio()。 @EnableScheduling 注解源码如下:

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

上面的 EnableScheduling 注解,我们可以看到它只是一个标准的类级注解(@Target/@Retention),应该包含在 JavaDocs 中(@Documented),但是它有一个 Spring 特有的注解(@Import)。 @Import 是将一切联系起来的关键。 在这种情况下,由于我们的 SchedulingConfig 被注解为 @EnableScheduling,当 BeanFactory 解析文件时(内部是ConfigurationClassPostProcessor 在解析它),它也会发现 @Import(SchedulingConfiguration.class) 注解,它将导入该值中定义的类。 在这个注解中,就是 SchedulingConfiguration。

这里导入是什么意思呢?在这种情况下,它只是被当作另一个 Spring Bean。 SchedulingConfiguration 实际上被注解为@Configuration,所以 BeanFactory 会把它看作是另一个配置类,所有在该类中定义的 Bean 都会被拉入你的应用上下文,就像你自己定义了另一个 @Configuration 类一样。 如果我们检查 SchedulingConfiguration,我们可以看到它只定义了一个Bean(一个Post Processor),它负责我们上面描述的调度工作,源码如下:

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration { @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
} }

也许你会问,如果想配置 SchedulingConfiguration 中定义的 bean 呢? 这里也只是在处理普通的Bean。 所以你对其它 Bean 所使用的机制也适用于此。 在这种情况下,ScheduledAnnotationBeanPostProcessor 使用一个标准的 Spring Bean 生命周期(postProcessAfterInitialization)来发现应用程序上下文何时被刷新。 当符合条件时,它会检查是否有任何 Bean 实现了 SchedulingConfigurer,如果有,就使用这些 Bean 来配置自己。 其实这一点并不明细(在 IDE 中也不太容易找到),但它与 BeanFactory 是完全分离的,而且是一个相当常见的模式,一个 Bean 被用来配置另一个 Bean。 而现在我们可以把所有的点连接起来,它(在某种程度上)很容易找到(你可以 Google 一下文档或阅读一下 JavaDocs)。

@EnableTransactionManagement(导入一个 ImportSelector)

在上一个示例中,我们讨论了像 @EnableScheduling 这样的注解如何使用 @Import 来导入另一个 @Configuration 类并使其所有的 Bean 对你的应用程序可用(和可配置)。但是如果你想根据某些配置加载不同的 Bean 集,会发生什么呢? @EnableTransactionManagement 就是一个很好的例子。TransactioConfig 定义如下:

@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class TransactioConfig {
// some bean in here
}

再一次,上面没有什么特别之处。只是一个用@EnableTransactionManagement注释的标准Java配置。唯一与之前的例子有些不同的是,用户为注释指定了一个参数(mode=AdviceMode.ASPECTJ)。 @EnableTransactionManagement注解本身看起来像这样。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE;
}

和前面一样,一个相当标准的注解,尽管这次它有一些参数。 然而,正如前文提到,@Import 注解是将一切联系在一起的关键,这一点再次得到证实。 但区别在于,这次我们导入的是 TransactionManagementConfigurationSelector 这个类,通过源码可以发现,其实它不是一个被 @Configuration 注解的类。 TransactionManagementConfigurationSelector 是一个实现ImportSelector 的类。 ImportSelector 的目的是让你的代码选择在运行时加载哪些配置类。 它有一个方法,接收关于注解的一些元数据,并返回一个类名数组。 在这种情况下,TransactionManagementConfigurationSelector 会查看模式并根据模式返回一些类。其中的 selectImports 方法源码如下:

@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}

这些类中的大多数是 @Configuration(例如 ProxyTransactionManagementConfiguration),通过前文介绍我们知道它们会像前面一样工作。 对于 @Configuration 类,它们被加载和配置的方式与我们之前看到的完全一样。 所以简而言之,我们可以使用 @Import 和 @Configuration 类来加载一套标准的 Bean,或者使用 @Import 和 ImportSelector 来加载一套在运行时决定的 Bean。

@EnableAspectJAutoProxy (在 Bean 定义层导入)

@Import 支持的最后一种情况,即当你想直接处理 BeanRegistry(工厂)时。如果你需要操作Bean Factory或者在Bean定义层处理Bean,那么这种情况就适合你,它与上面的情况非常相似。 你的 AspectJProxyConfig 可能看起来像。

@Configuration
@EnableAspectJAutoProxy
public class AspectJProxyConfig {
// some bean in here
}

再一次,上面定义没有什么特别的东西。只是一个用 @EnableAspectJAutoProxy 注释的标准 Java 配置。 下面是@EnableAspectJAutoProxy 的源代码。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false;
}

和前面一样,@Import 是关键,但这次它指向 AspectJAutoProxyRegistrar,它既没有 @Configuration 注解,也没有实现 ImportSelector 接口。 这次使用的是实现了 ImportBeanDefinitionRegistrar。 这个接口提供了对 Bean 注册中心(Bean Registry)和注解元数据的访问,因此我们可以在运行时根据注解中的参数来操作 Bean 注册表。 如果你仔细看过前面的示例,你可以看到我们忽略的类也是 ImportBeanDefinitionRegistrar。 在 @Configuration 类不够用的时候,这些类会直接操作 BeanFactory。

所以现在我们已经涵盖了 @EnableXXX 注解使用 @Import 将各种 Bean 引入你的应用上下文的所有不同方式。 它们要么直接引入一组 @Configuration 类,这些类中的所有 Bean 都被导入到你的应用上下文中。 或者它们引入一个 ImportSelector 接口实现类,在运行时选择一组 @Configuration 类并将这些 Bean 导入到你的应用上下文中。 最后,他们引入一个ImportBeanDefinitionRegistrars,可以直接与 BeanFactory 在 BeanDefinition 级别上合作。

结论

总的来说,个人认为这种将 Bean 导入应用上下文的方法很好,因为它使框架使用者的使用某个功能非常容易。不幸的是,它模糊了如何找到可用的选项以及如何配置它们。 此外,它没有直接利用 IDE 的优势,所以很难知道哪些 Bean 正在被创建(以及为什么)。 然而,现在我们知道了 @Import 注解,我们可以使用 IDE 来挖掘一下每个注解及其相关的配置类,并了解哪些 Bean 正在被创建,它们如何被添加到你的应用上下文中,以及如何配置它们。 希望对你有帮助~

Spring 中 @EnableXXX 注解的套路的更多相关文章

  1. Spring中@Import注解的使用

    Spring中@Import注解的使用 @Import注解算是SpringBoot自动配置原理中一个很重要的注解 认识@Import注解 先看一下源码 @Target(ElementType.TYPE ...

  2. Spring中@Autowired注解、@Resource注解的区别 (zz)

    Spring中@Autowired注解.@Resource注解的区别 Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@ ...

  3. Spring中Value注解的使用

    Spring中Value注解的使用 分类: Spring2014-08-16 17:28 2985人阅读 评论(0) 收藏 举报 有的时候我们定义了Properties文件,并且使用Spring的Pr ...

  4. 全面解析Spring中@ModelAttribute注解的用法

    本文不再更新,可能存在内容过时的情况,实时更新请移步我的新博客:全面解析Spring中@ModelAttribute注解的用法: @ModelAttribute注解用于将方法的参数或方法的返回值绑定到 ...

  5. EnableAutoConfiguration注解 Spring中@Import注解的作用和使用

    EnableAutoConfiguration注解 http://www.51gjie.com/javaweb/1046.html springboot@EnableAutoConfiguration ...

  6. Spring中常用注解的介绍

    spring中使用注解时配置文件的写法: <?xml version="1.0" encoding="UTF-8"?> <span style ...

  7. Spring中使用注解时启用<context:component-scan/>

    在spring中使用注解方式时需要在spring配置文件中配置组件扫描器:http://blog.csdn.net/j080624/article/details/56277315 <conte ...

  8. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...

  9. Spring中常用注解

    1.@Component 创建类对象,相当于配置<bean/> 2.@Service @Service与@Component功能相同,写在ServiceImpl类上 3.@Reposito ...

随机推荐

  1. UP9616移动电源快充案例

    第一版的UP9616快充(地址在此 ),从选料到线路板的布局走线都还算不错,实现了当初定下的设计目标,但也有一点小小的遗憾,就是在输出滤波电容这里有点随便了,为了弥补这个遗憾,秉着工程师的" ...

  2. 开发一个自己的 CSS 框架(四)

    这一节,我们来讲规矩,谈网格,做人可以不要脸,不讲规矩,不讲道理(特指傲娇兽),但底线还是要有的,如同网格一样,不能超出. jeet 这里我们别人封装好的模块,不过呢,我们也会详细介绍一下原理.首先我 ...

  3. zTree -- jQuery 树插件 使用方法与例子

    简介 zTree 是一个依靠 jQuery 实现的多功能 "树插件". 网址:http://www.ztree.me/v3/main.php#_zTreeInfo 上面的网址里有z ...

  4. 【Android开发】Webview 和 JS 交互问题

    一,安卓原生调用JS代码 1,js代码: function handlePasteDataFromApp(pasteStr) { showInfo('pasteData: aaaaa' + JSON. ...

  5. Hadoop搭建高可用的HA集群

    一.工具准备 1.7台虚拟机(至少需要3台),本次搭建以7台为例,配好ip,关闭防火墙,修改主机名和IP的映射关系(/etc/hosts),关闭防火墙 2.安装JDK,配置环境变量 二.集群规划: 集 ...

  6. 小程序checkbox调整大小(checkbox样式修改)

    .skyCheckbox{ transform: scale(0.7,0.7); -webkit-transform: scale(0.7,0.7); } <label class=" ...

  7. 实战-DRF快速写接口(认证权限频率)

    实战-DRF快速写接口 开发环境 Python3.6 Pycharm专业版2021.2.3 Sqlite3 Django 2.2 djangorestframework3.13 测试工具 Postma ...

  8. 前端之HTML标签

    一:HTML简介 1.超文本标记语言(Hypertext Markup Language, HTML)是一种用于创建网页的标记语言. 2.本质上是浏览器可识别的规则,我们按照规则写网页,浏览器根据规则 ...

  9. No package XXX available 解决方法

    当前yum源找不到所需包,安装epel源即可 yum install -y epel-release EPEL 的全称叫 Extra Packages for Enterprise Linux.EPE ...

  10. QT类使用记录

    QT类使用记录 1.QSharedMemory 提供了对一段共享内存的访问.既提供了被多进程和多线程共享的一段内存的访问.也为单线程或单进程锁定内存以实现互斥访问提供了方法. QSharedMemor ...