概述
Springboot
提供了很多@EnableXXX
这样的注解,通过使用这些注解,我们能够很方便地启用某些功能。那么这些@EnableXXX
注解又是怎么工作的呢?简单来讲,就是它主要利用了另外一个注解@Import
,而关于@Import
如何使用以及背后的工作原理,可以参考我的另外一篇文章 :Spring @Import 的使用及其工作原理分析。本文不再重点讲解@EnableXXX
,@Import
的工作原理,而是通过一个例子项目讲解注解@EnableXXX
如何定义及其工作效果。
自定义@EnableXXX注解的例子项目
演示目的
这个例子项目中,我们通过普通Java
类方式定义了一组服务组件,也就是说没有使用Spring
标准的bean定义方式将它们定义为可被Spring
框架直接识别的bean;然后我们自定义了一个@EnableXXX
注解,通过使用这个自定义的注解,我们能够把这些通过普通Java
类方式定义的组件注册为Spring
容器bean,从而使他们变得跟其他Spring
标准方式定义的bean一样被使用 。
例子项目的源代码
下面,我们来看一下这个项目的源代码。这是一个基于maven
的项目。
项目文件结构
/
│
│ pom.xml
│
└─src
└─main
└─java
├─app
│ Application.java
│
├─config
│ EnableMyOwnBeanDefinitions.java
│ LogServiceImportSelector.java
│ OrderServiceBeanDefinitionRegistrar.java
│ UserBeansConfig.java
│
└─services
AdminService.java
CustomerService.java
LogService.java
OrderChangeRecordService.java
OrderService.java
SettingService.java
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>app</groupId>
<artifactId>zero</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
</project>
模拟的服务组件类
在这个例子项目中,我们模拟了以下服务组件类,因为只是出于演示的目的,所以每个服务类组件并没有提供任何具体的实现。通过类名称和类上的注解,你应该能理解每个服务组件类想表达的意义。这里需要指出的是,这些服务组件类都是普通Java
类,没有使用任何Spring
标准bean定义方式将它们定义为bean,比如使用XML
bean定义方式,或者使用类似@Service
,@Component
,Bean
这样注解定义的方式。
你可能会问,既然我们能通过
@Service
,@Component
,Bean
这样标准的Spring
bean定义方式定义bean,那么这里又何苦使用普通Java
类方式定义服务组件类呢 ?
这里有两个目的。
一,本例子中这样定义的目的是为了演示一个@Enable
注解如何工作;
二,想象一下一个通过其他渠道获取的java服务包,你无法在源代码层面对其服务组件类进行修改,但你又想把它们引入Springboot
应用体系中,那该怎么办呢?等你读完了本文,再想一下,这里是不是就提供了一种不错的解决办法?
package services;
/**
* 管理员管理服务:管理员账号增删改查等
*/
public class AdminService {
}
package services;
/**
* 客户管理服务:客户账号,基础信息增删改查等
*/
public class CustomerService {
}
package services;
/**
* 日志服务
*/
public class LogService {
}
package services;
/**
* 订单变更跟踪记录,仅用在 开发模式下
*/
public class OrderChangeRecordService {
}
package services;
/**
* 订单服务:订单的增删改查等
*/
public class OrderService {
}
package services;
/**
* 配置服务:系统配置参数的增删改查等
*/
public class SettingService {
}
自定义@EnableMyOwnBeanDefinitions
准备一个@Configuration
配置类
package config;
import services.AdminService;
import services.CustomerService;
import org.springframework.context.annotation.Bean;
/**
* 一个 Spring 配置类, 注解方式注册bean的典型用法 :
*
* @Configuration 定义配置类 + @Bean 注解配置类方法注册bean
*/
//@Configuration
public class UserBeansConfig {
/**
* 注册管理员管理服务组件bean
* AdminService 是一个普通Java类
*
* @return
*/
@Bean
public AdminService adminService() {
return new AdminService();
}
/**
* 注册客户管理服务组件bean
* CustomerService 是一个普通Java类
*
* @return
*/
@Bean
public CustomerService customerService() {
return new CustomerService();
}
}
准备一个ImportSelector
package config;
import services.LogService;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* 一个 ImportSelector 实现类
* 用于演示通过 ImportSelector ,给定一组要注册为bean组件的普通Java类的名称,
* 然后通过利用 @Import(LogServiceImportSelector.class)即可注册相应的bean
*/
public class LogServiceImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回要注册成为 bean 的类的全名称的数组
return new String[]{LogService.class.getName()};
}
}
准备一个ImportBeanDefinitionRegistrar
package config;
import services.OrderChangeRecordService;
import services.OrderService;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;
/**
* 一个接口ImportBeanDefinitionRegistrar的实现类,
* 用于演示 , 演示点:
* 1. 程序化将普通Java类作为bean注册到Spring IoC容器;
* 2. 使用注解元数据属性动态决定bean的注册;
*/
public class OrderServiceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 注解元数据
* @param registry Spring IoC 容器
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(
EnableMyOwnBeanDefinitions.class.getName()));
{// 注册类型为 OrderService 的订单服务组件 bean
Class beanClass = OrderService.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
registry.registerBeanDefinition(beanName, beanDefinition);
}
{// 看情况决定是否注册类型为 OrderChangeRecordService 的订单变更记录服务组件 bean
boolean trackOrderChange = annotationAttributes.getBoolean("trackOrderChange");
if (trackOrderChange) {
Class beanClass = OrderChangeRecordService.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
}
定义注解 @EnableMyOwnBeanDefinitions
现在基于上面定义的配置类UserBeansConfig
(@Configuration
配置类),LogServiceImportSelector
(ImportSelector
),OrderServiceBeanDefinitionRegistrar
(ImportBeanDefinitionRegistrar
),我们自定义一个注解@EnableMyOwnBeanDefinitions
。
这个注解同时定义了一个属性trackOrderChange
,用来表明是否要开启订单变动跟踪,这个语义在上面的OrderServiceBeanDefinitionRegistrar
中被具体实现:如果trackOrderChange
为false
,服务组件OrderChangeRecordService
并不会被注册到Spring
容器。
package config;
import services.SettingService;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 模仿 Spring 框架的 @EnableXXX 注解自定义的一个 @Enable 注解,
* 此类注解一般使用 @Import 通过以下四种方式进行 bean 定义:
* 1. @Configuration 注解的专门用于bean定义的类,一般通过@Bean注解的方法注册bean ;
* 2. ImportSelector 给出某些要注册为bean的普通类的类名,将它们注册为 bean ;
* 3. ImportBeanDefinitionRegistrar 直接基于某些普通类创建 BeanDefinition 并注册相应的 bean ,可以有较复杂的逻辑;
* 4. 直接将某个普通类作为一个bean注册到容器。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({
UserBeansConfig.class,
LogServiceImportSelector.class,
OrderServiceBeanDefinitionRegistrar.class,
SettingService.class})
public @interface EnableMyOwnBeanDefinitions {
/**
* 注解属性:是否记录订单变更,语义 :
* false -- 不注册订单变更记录服务组件bean
* true -- 注册订单变更记录服务组件bean
*
* @return
*/
boolean trackOrderChange() default false;
}
使用自定义注解@EnableMyOwnBeanDefinitions
package app;
import config.EnableMyOwnBeanDefinitions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 演示自定义注解@EnableMyOwnBeanDefinitions的使用和效果
*/
@EnableMyOwnBeanDefinitions(trackOrderChange = true)
//@EnableMyOwnBeanDefinitions(trackOrderChange = false)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
dumpBeansToConsole(applicationContext);
}
/**
* 往控制台上输出容器中注册的各个bean的名称
*
* @param applicationContext
*/
private static void dumpBeansToConsole(ConfigurableApplicationContext applicationContext) {
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
Object bean = applicationContext.getBean(name);
System.out.printf("%s[%s]\n", name, bean.getClass().getName());
}
}
}
现在,当我们运行上面的应用时,控制台上会输出我们通过Java普通类所定义的服务组件bean,如下所示:
config.UserBeansConfig[config.UserBeansConfig]
adminService[services.AdminService]
customerService[services.CustomerService]
services.LogService[services.LogService]
services.SettingService[services.SettingService]
orderService[services.OrderService]
orderChangeRecordService[services.OrderChangeRecordService]
把注解@EnableMyOwnBeanDefinitions
的属性trackOrderChange
设置为false
,再运行上面的应用,控制台上的输出会变成这个样子:
config.UserBeansConfig[config.UserBeansConfig]
adminService[services.AdminService]
customerService[services.CustomerService]
services.LogService[services.LogService]
services.SettingService[services.SettingService]
orderService[services.OrderService]
跟上面属性trackOrderChange
设置为true
时的输出相比,可以看到orderChangeRecordService[services.OrderChangeRecordService]
不见了,这说明开关属性trackOrderChange
在起到相应的作用。
总结
本文通过一个实际的例子项目演示了如何定义一个@EnableXXX
注解并应用在Springboot
项目中。通过这个例子,你可以看到@EnableXXX
是怎样被定义的以及起到了什么样的作用。关于@EnableXXX
背后的工作原理,基本上也就是注解@Import
的工作原理,你可以参考我的另外一篇文章 :Spring @Import 的使用及其工作原理分析。另外,通过这个例子,你也可以针对某些第三方提供的java服务包提供一个自定义的@EnableXXX
注解将这些服务注册到Spring
的应用环境中来。