【Spring源码】- 09 扩展点之@Import注解

这节我们主要来分析下@Import注解,其是在Spring 3.0开始引入,是Spring中非常重要的一个注解,特别在第三方模块和Spring进行整合场景下使用非常频繁,比如上节分析的mybatis-spring模块实现mybatisspring整合,就是利用@Import(MapperScannerRegistrar.class)引入MapperScannerRegistrar这个关键的BeanDefinitionRegistryPostProcessor扩展类,实现扫描Mapper类,并注册到IoC中,SpringBootSpringCloud等都有@Import注解大量使用场景。

基本使用

@Import注解定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
 Class[] value();
}

该注解只有一个Class类型属性,指定需要处理的类,主要分为三种类型:普通类、ImportSelectorImportBeanDefinitionRegistrar

普通类

首先,其可以导入一个普通类,引入普通类是,Spring内部处理时只是把普通类当成一个Configuration Class,并进行递归方式重新解析该配置类。这里有两种场景:

  • 普通类上没有注解,比如:@Import(TestService.class)TestService类上没有定义任何注解,这种是最简单的情形,Spring会把TestService导入并注册到IoC容器中;

  • @Import普通类时,Spring会把其当成一个配置类进行处理,如果其上面带有注解会继续被解析处理的,比如@Import导入的Class上含有@Configuration@ComponentScan,这些注解会被解析处理;

ImportSelector

@Import注解可以引入ImportSelector类型,ImportSelector是从Spring 3.1开始引入的接口。如下:可以利用编程方式动态向IoC容器中注册BeanImportSelector可以读取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容器中。

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标注的ClassSpring中会被认为是配置类,配置类主要通过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引入三种类型分别对应三种处理方式:

  1. 如果非ImportSelectorImportBeanDefinitionRegistrar实现类,就会被当成普通方式处理,走方式三进入processConfigurationClass()方法,即被当成配置类进行递归处理;

  2. 如果是ImportSelector类型,走方式一处理,调用ImportSelector#selectImports()方法获取到返回数组,然后对返回值进行递归处理;

  3. 如果是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 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
当前测试线程:main
TestService->test02 execute:main
TestService->test01 execute:Async-executor-0

从输入结果可以看到,test01()方法使用线程池异步调用,而test02()方法没有标注@MyAsync注解,依然使用同步调用方式执行。

总结

@Import注解是Spring中非常重要的一个扩展点,在SpringBootSpringCloud中有大量的应用,并通过源码分析了@Import注解在Spring中的处理机制。

在分析过程中引入了两个案例,第一个案例了解了SpringBoot中大量使用的@EnableXXX注解的一个基本实现逻辑;第二个案例:@Import注解、IoC容器扩展点、AOP等技术融合大致了解下Spring@EnableAsync@Async注解运行背后的原理。

长按识别关注,持续输出原创  

【Spring源码】- 09 扩展点之@Import注解_第1张图片

你可能感兴趣的:(【Spring源码】- 09 扩展点之@Import注解)