这节我们主要来分析下@Import注解,其是在 Spring 3.0开始引入,是Spring中非常重要的一个注解,特别在第三方模块和 Spring进行整合场景下使用非常频繁,比如上节分析的mybatis-spring模块实现 mybatis和spring整合,就是利用 @Import(MapperScannerRegistrar.class)引入MapperScannerRegistrar这个关键的
BeanDefinitionRegistryPostProcessor扩展类,实现扫描Mapper类,并注册到 IoC中,SpringBoot、 SpringCloud等都有@Import注解大量使用场景。
基本使用
@Import注解定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented
public @interface Import { Class<?>[] value();}
该注解只有一个Class类型属性,指定需要处理的类,主要分为三种类型:普通类、 ImportSelector和ImportBeanDefinitionRegistrar。
普通类
首先,其可以导入一个普通类,引入普通类是,Spring内部处理时只是把普通类当成一个 Configuration Class,并进行递归方式重新解析该配置类。这里有两种场景:
-
普通类上没有注解,比如:
@Import(TestService.class),TestService类上没有定义任何注解,这种是最简单的情形,Spring会把TestService导入并注册到IoC容器中; -
@Import普通类时,Spring会把其当成一个配置类进行处理,如果其上面带有注解会继续被解析处理的,比如@Import导入的Class上含有@Configuration、@ComponentScan,这些注解会被解析处理;
ImportSelector
@Import注解可以引入 ImportSelector类型,ImportSelector是从 Spring 3.1开始引入的接口。如下:可以利用编程方式动态向IoC容器中注册 Bean。ImportSelector可以读取 annotation的属性来决定要加载哪些Configuration。下面我们看一个
ImportSelector的具体实现:EnableConfigurationPropertiesImportSelector。
class EnableConfigurationPropertiesImportSelector
implements ImportSelector { @Override
public String[] selectImports(AnnotationMetadata metadata) { // 获取EnableConfigurationProperties注解的所有的属性值 MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes( EnableConfigurationProperties.class.getName(),
false); // 获取attributes中主键value的第一个值。 Object[] type = attributes ==
null ? null : (Object[]) attributes.getFirst(
"value"); // 根据条件,返回需要导入的类。
if (type == null || type.length == 0) {
return new String[] { ConfigurationPropertiesBindingPostProcessorRegistrar.class .getName() }; }
return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; }}
该类来自于SpringBoot的一个实现,用于绑定外部配置文件中的属性到 Configuration类中。
注意:如果没有返回应该返回空数组
return new String[0],而不能返回null,否则报空指针异常。
下面我们就来使用ImportSelector实现类方式,实现SpringBoot中常见的@Enable***注解方式。
1、自定义一个ImportSelector实现类:
public class
MyImportSelector implements ImportSelector {
@Override public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{TestService.class.getName()}; }}
2、定义一个@Enable***注解,方便使用:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)
@Documented@Import(MyImportSelector.class)public
@interface EnableService {}
3、将@Enable***注解添加到配置类上:
@Configuration@EnableService
public class
TestConfiguration {}
通过上面简单的方式,就可以把ImportSelector#selectImports()方法返回的字符串数组对应的 Class注册到IoC容器中。
ImportBeanDefinitionRegistrar
ImportSelector是利用 selectImports()方法返回字符串数组进行IoC容器注册, ImportBeanDefinitionRegistra则更加灵活,可以获取到IoC容器,利用 registerBeanDefinition()方法直接注入Bean。
如下,直接将Service2解析的 BeanDefinition注册到IoC容器中:
public class
MyImportBeanDefinitionRegistrar implements
ImportBeanDefinitionRegistrar { @Override
public void
registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition beanDefinition =
new RootBeanDefinition(Service2.class); registry.registerBeanDefinition(
"service2", beanDefinition); }}
源码解析
@Import标注的 Class在Spring中会被认为是配置类,配置类主要通过 ConfigurationClassPostProcessor这个类进行解析,所以@Import注解解析处理入口就是这个类。 ConfigurationClassPostProcessor内部会使用ConfigurationClassParser解析器去解析配置类,沿着这个方法一直跟踪下去发现
@Import注解是在processImports()方法中进行处理的:
if (candidate.isAssignable(ImportSelector.class)) {
//方式一 Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment,
this.resourceLoader,
this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter !=
null) { exclusionFilter = exclusionFilter.or(selectorFilter); }
if (selector
instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); }
else {
//这里会调用ImportSelector#selectImports()方法获取到返回数组 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//1.selectImports返回的是要动态注册的bean名称 Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
//这里递归调用,有为ImportSelector引入的可能不一定是普通类,还可以又是ImportSelector、ImportBeanDefinitionRegistrar,需要继续递归处理
//如果是普通类,则走到方式三处理,ImportBeanDefinitionRegistrar类型则走到方式二处理,ImportSelector则继续递归处理 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter,
false); }}
else
if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
//方式二 Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment,
this.resourceLoader,
this.registry);
/** * 调用configClass.addImportBeanDefinitionRegistrar方法将ImportBeanDefinitionRegistrar实现类存入configClass的成员变量importBeanDefinitionRegistrars中, * 后面的ConfigurationClassPostProcessor类的processConfigBeanDefinitions方法中,this.reader.loadBeanDefinitions(configClasses);会调用这些ImportBeanDefinitionRegistrar实* 现类的registerBeanDefinitions方法: */ configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}
else {
//方式三
this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
//3.迭代调用processImport方法后,会直接到这里,configClass是步骤1返回的bean名称}
从上面源码可以看出 @Import引入三种类型分别对应三种处理方式:
-
如果非
ImportSelector和ImportBeanDefinitionRegistrar实现类,就会被当成普通方式处理,走方式三进入processConfigurationClass()方法,即被当成配置类进行递归处理; -
如果是
ImportSelector类型,走方式一处理,调用ImportSelector#selectImports()方法获取到返回数组,然后对返回值进行递归处理; -
如果是
ImportBeanDefinitionRegistrar类型,则走方式二处理流程,会创建出ImportBeanDefinitionRegistrar实例然后缓存到configClass中,等待后续reader.loadBeanDefinitions(configClasses)中进行处理。
reader.loadBeanDefinitions(configClasses)方法中 loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars())这句就是用来处理上面解析的ImportBeanDefinitionRegistrar类型逻辑:
private void loadBeanDefinitionsFromRegistrars
(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata,
this.registry, this.importBeanNameGenerator));}
就是把IoC容器和 configClass的元信息传入进行回调ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry, BeanNameGenerator),这样,你就可以获取到配置类上注解信息,然后根据这些配置向 IoC容器中注入BeanDefinition即可。
案例
上面对@Import注解的基本使用和源码解析逻辑进行了分析,下面我们通过一个案例拓展下你对 @Import注解使用场景的认识。
背景:Spring 3.0 之后分别引入了@EnableAsync和 @Async这两个注解,可以简单的就把一个普通方法变成异步机制执行,应用中通过@EnableAsync开启异步 @Async注解支持后,然后再方法上添加@Async注解后就变成了异步执行方法:
@Componentpublic class
AsyncTask { @Async public
void doTask(){ log.info("Thread {} ", Thread.currentThread().getName());
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); } log.info(
"Thread {} ", Thread.currentThread().getName()); }}
通过两个简单的注解方法,就可以把一个同步方法变成异步执行方法,是不是很神奇,下面我们就来通过案例了解下这背后实现的原理。
1、先来定义两个注解,@MyEnableAsync控制功能开关, @MyAsync控制哪些方法需要该功能:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AsyncConfigImportRegistrar.class)public @interface MyEnableAsync { //proxyTargetClass=true直接对类进行代理,即使用cglib;proxyTargetClass=false则使用jdk代理 boolean proxyTargetClass() default false; //指定异步执行线程池 String executor() default "defaultExecutor";}
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyAsync {}
2、@MyEnableAsync注解通过 @Import(AsyncConfigImportRegistrar.class)导入AsyncConfigImportRegistrar,下面来定义下这个类:
public class AsyncConfigImportRegistrar
implements ImportBeanDefinitionRegistrar { @Override
public void registerBeanDefinitions
(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/** * importingClassMetadata封装了配置类元信息,通过getAnnotationAttributes()可以获取到配置类上指定注解属性Map */ AnnotationAttributes attributes = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MyEnableAsync.class.getName())); Assert.notNull(attributes,
"@MyEnableAsync attributes is null"); RootBeanDefinition beanDefinition =
new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); MutablePropertyValues pvs = beanDefinition.getPropertyValues(); pvs.addPropertyValue(
"proxyTargetClass", attributes.getBoolean("proxyTargetClass")); pvs.addPropertyValue(
"executor", new RuntimeBeanReference(attributes.getString("executor"))); registry.registerBeanDefinition(
"asyncAnnotationBeanPostProcessor", beanDefinition); }}
这是一个ImportBeanDefinitionRegistrar实现类,该方法中主要注入 AsyncAnnotationBeanPostProcessor,并把注解相关配置通过PropertyValue方式注入进去。
3、接着我们就来定义AsyncAnnotationBeanPostProcessor:
public class
AsyncAnnotationBeanPostProcessor extends ProxyConfig
implements BeanPostProcessor {
private ExecutorService executor; @Override
public Object postProcessAfterInitialization
(Object bean, String beanName) {
//遍历出带有@MyAsync注解方法 MethodIntrospector.MetadataLookup<MyAsync> lookup = method -> AnnotatedElementUtils.findMergedAnnotation(method, MyAsync.class); Map<Method, MyAsync> methods = MethodIntrospector.selectMethods(bean.getClass(), lookup);
/** * 如果Bean含有@MyAsync注解方法,则对Bean进行代理,并对
@MyAsync注解方法进行增强 */
if(methods != null && !methods.isEmpty()){
//定义一个注解切点,只对@MyAsync注解方法进行切入 AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAsync.class);
//定义一个Advice,封装增强逻辑代码 Advice advice =
new AsyncAnnotationAdvice(executor);
//使用一个Advisor将增强逻辑Advice和切点PointCut封装到一起 DefaultPointcutAdvisor advisor =
new DefaultPointcutAdvisor(); advisor.setPointcut(pointcut); advisor.setAdvice(advice);
//创建一个Spring代理工具类 ProxyFactory proxyFactory =
new ProxyFactory();
//指定代理目标实例 proxyFactory.setTarget(bean);
if(!this.isProxyTargetClass()){
//使用jdk代理方式 proxyFactory.setInterfaces(bean.getClass().getInterfaces()); }
//advisor包含了织入点和织入逻辑,ProxyFactory就会根据这些创建出代理对象 proxyFactory.addAdvisor(advisor);
//使用当前类的属性 proxyFactory.copyFrom(
this);
return proxyFactory.getProxy(); }
return bean; }
public
void setExecutor
(ExecutorService executor) {
this.executor = executor; }}
AsyncAnnotationBeanPostProcessor是一个 BeanPostProcessor扩展点,之前分析过,其在Bean执行init-method前后进行扩展。这里主要利用Bean执行完init初始化方法后进行扩展:
-
遍历
Bean方法,查找到带有@MyAsync注解方法; -
查找到则对
Bean进行代理,否则返回原Bean; - 具体增强逻辑见上述代码注释;
4、步骤3中使用到了 AsyncAnnotationAdvice,就是对具体增强逻辑代码封装:
public
class
AsyncAnnotationAdvice
implements
MethodInterceptor {
private ExecutorService executor;
public
AsyncAnnotationAdvice
(ExecutorService executor)
{
this.executor = executor; }
public Object
invoke
(
final MethodInvocation invocation)
throws Throwable {
//对目标方法调用封装到一个Callable实例中 Callable<Object> task = () -> {
try { Object result = invocation.proceed();
if (result
instanceof Future) {
return ((Future<?>) result).get(); } }
catch (Throwable e) { e.printStackTrace(); }
return
null; };
//然后放入到线程池中执行
if(executor !=
null){ executor.submit(task); }
else{ invocation.proceed(); }
return
null; }}
从上面代码可以看出,增强逻辑就是把对目标方法调用封装到一个 Callable实例中,然后放入到一个线程池中执行。所以,这样就可以把一个普通方法调用编程异步方法调用。
上面就把具体实现逻辑都完成了,下面我们来测试是否正常执行。
1、定义 Service接口及其实现类 TestService,定义接口主要是用于测试 jdk动态代理方式:
public
interface
Service {
void
test01
()
;
void
test02
()
;}
@Component
public
class
TestService
implements
Service
{
//测试异步调用
@MyAsync
public
void
test01
()
{ System.out.println(
"TestService->test01 execute:"+Thread.currentThread().getName()); }
//没有注解,还是执行同步调用
public
void
test02
()
{ System.out.println(
"TestService->test02 execute:"+Thread.currentThread().getName()); }}
2、定义一个启动配置类:
@Configuration
@ComponentScan(basePackageClasses = TestConfiguration.class)
@MyEnableAsync(proxyTargetClass =
true)
public
class
TestConfiguration {
@Bean
public ExecutorService
defaultExecutor
()
{
final ThreadFactory threadFactory =
new ThreadFactoryBuilder() .setNameFormat(
"Async-executor-%d") .setDaemon(
true) .build();
return Executors.newFixedThreadPool(
5, threadFactory); }}
在启动配置类中使用 @MyEnableAsync(proxyTargetClass
=
true)开启功能,
proxyTargetClass=true表示使用 cglib代理方式,默认 false即使用 jdk动态代理方式。 @MyEnableAsync注解另一个属性 executor可以用于指定使用哪个线程池,默认使用 defaultExecutor。
3、定义测试类:
public
class
AsyncTest {
@Test
public
void
test01
()
throws ExecutionException, InterruptedException { AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TestConfiguration.class); Service testService = context.getBean(Service.class); System.out.println(
"TestService Class:"+testService.getClass()); System.out.println(
"当前测试线程:"+Thread.currentThread().getName()); testService.test01(); testService.test02(); TimeUnit.SECONDS.sleep(
2); }}
输出结果:
TestService
Class:class
async.TestService?EnhancerBySpringCGLIB?26022d2a当前测试线程:mainTestService->test02
execute:mainTestService->test01
execute:Async-executor-0
从输入结果可以看到, test01()方法使用线程池异步调用,而 test02()方法没有标注 @MyAsync注解,依然使用同步调用方式执行。
总结
@Import注解是 Spring中非常重要的一个扩展点,在 SpringBoot、 SpringCloud中有大量的应用,并通过源码分析了 @Import注解在 Spring中的处理机制。
在分析过程中引入了两个案例,第一个案例了解了 SpringBoot中大量使用的 @EnableXXX注解的一个基本实现逻辑;第二个案例: @Import注解、 IoC容器扩展点、 AOP等技术融合大致了解下 Spring中 @EnableAsync和 @Async注解运行背后的原理。
长按识别关注, 持续输出原创