@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
接口,该接口中只有一个方法:
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
接口中也只有一个方法:
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作用。
这次 @Import
最常见是使用方法。我们可以拆分配置类,然后在程序中按需导入相应的配置。
举个例子:例如 @EnableRetry
注解。
使用这个注解可以开启retry功能。
@EnableAspectJAutoProxy(proxyTargetClass = false) @Import(RetryConfiguration.class) public @interface EnableRetry { 复制代码
其内部就是导入了 RetryConfiguration
这个配置类。
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学习一下。