@EnableXxx註解
Spring有很多@EnableXxx
這種形式的註解,類似於可以一鍵打開某項功能,相當於暴露給用戶的一種便捷的配置API,例如 @EnableAsync
激活異步執行能力,@EnableTransactionManagement
開啓方法事務管理能力。
如果自己也想編寫了一個叫做@EnaleHealthCheck
的註解,可以一鍵打開程序的健康檢測功能。編寫這種註解本質上是對 @Import
的使用,通過 @Import
背後的框架基礎設施實現必要的bean的裝配,理解 @EnablbeXxx
需要知道 @Import
。
@Import及ImportSelector,ImportBeanDefinitionRegistrar
@Import 可以和 @Configuration 一起使用,它通常用於導入另一個@Configuration配置類,功能和xml schame配置的 <import/>
相同,也可以把 @Import 當作元註解使用擴展出更方便和具體的註解,@EnablbeXxx 就是這樣的。
@Import 註解的 value() 屬性接受三種類型:
- @Configuration
- ImportSelector
- ImportBeanDefinitionRegistrar
(1)直接提供一個 @Configuration 配置類給 @Import:
@Configuration
@Import(HealthCheckConfig.class) //HealthCheckConfig配置健康檢測相關的bean
public class AppConfig {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
}
}
//HealthCheckConfig
@Configuration
public class HealthCheckConfig {
@Bean
public HealthCheckBean healthCheckBean() {
return new HealthCheckBean();
}
}
這個示例運行後,Spring容器中會存在一個 HealthCheckBean 對象用於提供健康檢測功能。
(2)提供一個 ImportSelector 實現類給 @Import:
這種用法是通過 selectImports()
方法返回一組 @Configuration 配置類,這比直接提供一個 @Configuration 配置類更靈活,通過實現 ImportSelector 接口,用戶可以用java代碼控制導入哪些配置,具有更靈活的運行行爲。
//主配置類
@Configuration
@Import(EitherXOrYImportSelector.class)
public class AppConfig {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
//容器啓動成功後,XBean或者YBean任意會存在一個
}
}
@Configuration
public class XBeanConfig {
@Bean
public XBean xbean() {
return new XBean();
}
}
@Configuration
public class YBeanConfig {
@Bean
public YBean ybean() {
return new YBean();
}
}
//二選一的ImportSelector實現
public class EitherXOrYImportSelector implements ImportSelector {
//selectImports通常返回需要的@Configuration配置類全限定名稱
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
int r = new Random().nextInt(2);
if (r == 0)
return new String[]{XBeanConfig.class.getCanonicalName()};
else
return new String[]{YBeanConfig.class.getCanonicalName()};
}
}
(3)把@Import當元註解使用形成@EnableXxx:
@EnableXxx
類似的註解就是擴展 @Import 註解的,上述例子中如果想要實現一個 @EnableEither 註解,直接對兩個對象二選一進行註冊,可以這樣使用 @Import:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EitherXOrYImportSelector.class) //用Import導入EitheXOrYImportSelector
public @interface EnableEither {
// 設置爲"x",則在容器中註冊XBean,設置爲"y",則在容器中註冊YBean
String beanName() default "";
}
public class EitherFooOrBarImportSelector implements ImportSelector {
//通常返回需要的@Configuration配置類
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Map<String, Object> annotationAttributes =
importingClassMetadata.getAnnotationAttributes(EnableEither.class.getCanonicalName());
String beanName = (String) annotationAttributes.get("beanName");
if ("x".equalsIgnoreCase(beanName))
return new String[]{XBeanConfig.class.getCanonicalName()};
else if ("y".equalsIgnoreCase(beanName))
return new String[]{YBeanConfig.class.getCanonicalName()};
else
return new String[]{};
}
}
// 現在直接使用@EnableEither`註解即可開啓二選一註冊
@Configuration
@EnableEither(beanName = "y") //容器中會註冊YBean
public class AppConfig {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
XOrYObject o = context.getBean(XOrYObject.class);
System.out.println(o instanceof XBean);
}
}
@Import的原理分析
@Import
要結合 AnnotationConfigApplicationContext
容器使用,背後仍然是BeanFactoryPostProcessor 在發揮作用,在 refresh
過程中,會調用所有註冊的 BeanFactoryPostProcessor,其中一個是ConfigurationClassPostProcessor
, 它專門用於解析 @Configuration 配置元數據並註冊BeanDefinition,具體可以跟蹤 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 接口方法的實現方法。