zoukankan      html  css  js  c++  java
  • Spring4-@Enable** 注解的实现原理

    背景

    在前面的工作中使用SpringBoot的时候,我碰到了很多的使用@Enable***注解的地方,使用上也都是加在@Configuration 类注解的类上面,比如: 
    (1)@EnableAutoConfiguration 开启自动扫描装配Bean

    (2)@EnableScheduling 开启计划任务的支持

    (3)@EnableTransactionManagement 开启注解式事务的支持。

    (4)@EnableCaching开启注解式的缓存支持。

    (5)@EnableAspectJAutoProxy 开启对AspectJ自动代理的支持,

    还有一些我没用过,但是听过的,比如:

    (6) @EnableAsync 开启异步方法的支持

    (7) @EnableWebMvc 开启Web MVC的配置支持。

    (8) @EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持。

    (9)@EnableJpaRepositories 开启对Spring Data JPA Repository的支持。

    通过简单的加上这些@Enable*注解就可以开启一些功能,避免了在XML时代, 需要自己手动的配置很多的XML代码,大大提升效率降低使用难度。 那么这种简单的操作带来的巨大的便利性是怎么实现的呢?

    我研究了一下上面的这些自动开启某些功能的注解,发现了一些公共的特性,下面先举出一个例子:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(CachingConfigurationSelector.class)
    public @interface EnableCaching {
        boolean proxyTargetClass() default false;
        AdviceMode mode() default AdviceMode.PROXY;
        int order() default Ordered.LOWEST_PRECEDENCE;
    }

    在这些所有的@Enable*注解的定义里面都包含一个 @Import 注解。 @Import注解在4.2之前只支持导入配置类,在4.2,@Import注解支持导入普通的java类,并将其声明成一个bean。这也说明了,自动开启的实现,其实是导入了一些配置类。

    在上面各种@Enable**注解的实现中,导入配置类的形式,主要有三种类型:

    1. 根据条件选择配置类(用的最多的)

    以@EnableTransactionManagement 的定义为例:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(TransactionManagementConfigurationSelector.class)
    public @interface EnableTransactionManagement {
        boolean proxyTargetClass() default false;
        AdviceMode mode() default AdviceMode.PROXY;
        int order() default Ordered.LOWEST_PRECEDENCE;
    }

    TransactionManagementConfigurationSelector通过条件选择需要导入的配置类, TransactionManagementConfigurationSelector最根接口是ImportSelector, 这个接口重写了selectImports方法,在此方法里面先进行判断。比如在此例中,若AdviceMode为PROXY(默认), 则返回AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration 的配置类。

    源码如下:

    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;
            }
        }
    }

    2. 动态注册的Bean

    以@EnableAspectJAutoProxy 为例:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
        boolean proxyTargetClass() default false;
        boolean exposeProxy() default false;
    }

    AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar的作用是在运行时自动添加Bean到已有的配置类,并在Spring容器启动时解析生成bean,通过重写方法:

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

    来实现扫描注册逻辑。其中,AnnotationMetadata参数用来获得当前配置类上的注解,BeanDefinitionRegistry 参数用来注册Bean。源码如下:

    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
            AnnotationAttributes enableAspectJAutoProxy =
                    AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

    3. 直接导入配置类

    以@EnableScheduling 注解为例

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(SchedulingConfiguration.class)
    @Documented
    public @interface EnableScheduling {
    
    }

    从上面可以看到,直接导入了入配置类SchedulingConfiguration, 这个类注解了@Configuration,且注册了一个scheduledAnnotationProcessor的Bean,SchedulingConfiguration的源码如下:

    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class SchedulingConfiguration {
    
        @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
        @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
        public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
            return new ScheduledAnnotationBeanPostProcessor();
        }
    }

    总结: 其实上面的三种方法中,第一类是用的最多的。

  • 相关阅读:
    小程序底部弹出式导航菜单
    Markdown-it-latex2img
    Typography convention
    排版规约
    Java String的相关性质分析
    switch表达式中可以用哪些类型
    break,continue和return的区别
    一个.java文件中有多少个类(不是内部类)?
    RabbitMQ启动配置中出现(ArgumentError) argument error xxx的错误
    问题:IDEA部署Springboot项目的时候,提示很多xxx程序包找不到
  • 原文地址:https://www.cnblogs.com/xingzc/p/9462316.html
Copyright © 2011-2022 走看看