Spring中@Import的各種用法以及ImportAware介面
@Import 註解
@Import
註解提供了和XML中<import/>
元素等價的功能,實現匯入的一個或多個配置類。@Import
即可以在類上使用,也可以作為元註解使用。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class<?>[] value(); }
註解中只有一個value();
。支援匯入@Configuration
標註的配置類,實現ImportSelector
介面的類、實現ImportBeanDefinitionRegistrar
介面的類和普通的@component
類。
作為元註解使用
@Import
可以作為元註解使用,可以在@Import
的繼承上封裝一層。我的理解是,這樣做不會對外(使用方)暴露我內部的具體實現細節。
舉個例子:例如@EnableAspectJAutoProxy
註解。
@Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy {
@EnableAspectJAutoProxy
就是被@Import
這個元註解所標誌了,我們(程式設計師)通過使用@EnableAspectJAutoProxy
來開啟AspectJAutoProxy,而Spring底層是通過@Import
匯入相應的配置類來實現的。
匯入實現ImportSelector介面的類
先來看一下ImportSelector
介面,該介面中只有一個方法:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector,輸入選擇器。該介面就是用來根據給定的條件,選擇匯入哪些配置類。
舉個例子:例如@EnableTransactionManagement
註解。
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
在@EnableTransactionManagement
註解中使用了@Import(TransactionManagementConfigurationSelector.class)
註解,其中TransactionManagementConfigurationSelector
類就是實現了ImportSelector
介面。
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {
TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
方法的內部實現邏輯也很簡單,就是根據不同的AdviceMode
匯入不同的配置類,來實現事務管理。
匯入實現ImportBeanDefinitionRegistrar介面的類
ImportBeanDefinitionRegistrar
介面中也只有一個方法:
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
該介面允許我們根據所給的註解元資料,按需註冊額外的BeanDefinition
。
舉個例子:例如@EnableAspectJAutoProxy
註解。
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
@EnableAspectJAutoProxy
註解引入了AspectJAutoProxyRegistrar.class
類,這個類就是實現了ImportBeanDefinitionRegistrar
介面。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
registerBeanDefinitions
中呼叫了AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
方法,這個方法就是在往傳入的BeanDefinitionRegistry registry
中註冊BeanDefinition
。註冊了BeanDefinition
之後,Spring就會去例項化這個Bean,從而達到AspectJAutoProxy作用。
匯入@Configuration類
這次@Import
最常見是使用方法。我們可以拆分配置類,然後在程式中按需匯入相應的配置。
舉個例子:例如@EnableRetry
註解。使用這個註解可以開啟retry功能。
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
public @interface EnableRetry {
其內部就是匯入了RetryConfiguration
這個配置類。
ImportAware介面
ImportAware
介面是需要和@Import
一起使用的。在@Import
作為元註解使用時,通過@Import
匯入的配置類如果實現了ImportAware
介面就可以獲取到匯入該配置類介面的資料配置。有點繞,我們直接上程式碼。
舉個例子:@EnableAsync
註解。
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
//AsyncConfigurationSelector原始碼
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
預設情況下使用AdviceMode
為PROXY
,匯入了ProxyAsyncConfiguration
類。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
if (this.executor != null) {
bpp.setExecutor(this.executor);
}
if (this.exceptionHandler != null) {
bpp.setExceptionHandler(this.exceptionHandler);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;
}
}
在ProxyAsyncConfiguration
的asyncAdvisor
方法中需要獲取到@EnableAsync
上的一些設定值,例如:this.enableAsync.getBoolean("proxyTargetClass")
,this.enableAsync.<Integer>getNumber("order")
。
this.enableAsync
是其父類AbstractAsyncConfiguration
的屬性。AbstractAsyncConfiguration
實現了ImportAware
介面,從而就可以獲取到@EnableAsync
上的資訊了。
// AbstractAsyncConfiguration#setImportMetadata 原始碼
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableAsync = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableAsync.class.getName(), false));
if (this.enableAsync == null) {
throw new IllegalArgumentException(
"@EnableAsync is not present on importing class " + importMetadata.getClassName());
}
}
可能這個例子有點複雜的,還有一個稍微簡單一點的例子:EnableRedisHttpSession
。關於這個,讀者可以自己去看一下原始碼debug學習一下。
歡迎關注公眾號,大家一起學習成長。