例子講解Springboot註解@EnableXXX如何工作
概述
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
的應用環境中來。