这节我们主要来分析下@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
,这些注解会被解析处理;
@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 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
容器中。
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 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 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 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
注解后就变成了异步执行方法:
@Component
public 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)
@Documented
public @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 lookup
= method -> AnnotatedElementUtils.findMergedAnnotation(method, MyAsync.class);
Map 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
从上面代码可以看出,增强逻辑就是把对目标方法调用封装到一个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
当前测试线程:main
TestService->test02 execute:main
TestService->test01 execute:Async-executor-0
从输入结果可以看到,test01()
方法使用线程池异步调用,而test02()
方法没有标注@MyAsync
注解,依然使用同步调用方式执行。
@Import
注解是Spring
中非常重要的一个扩展点,在SpringBoot
、SpringCloud
中有大量的应用,并通过源码分析了@Import
注解在Spring
中的处理机制。
在分析过程中引入了两个案例,第一个案例了解了SpringBoot
中大量使用的@EnableXXX
注解的一个基本实现逻辑;第二个案例:@Import
注解、IoC
容器扩展点、AOP
等技术融合大致了解下Spring
中@EnableAsync
和@Async
注解运行背后的原理。
长按识别关注,持续输出原创