SpringBoot5-spring高级话题-组合注解与元注解,@Enable*注解的工作原理,测试

一:组合注解与元注解

      从spring2开始,为了响应JDK1.5推出的注解供暖,spring开始大量加入注解来替代xml配置。spring的注解主要用来配置和注入bean,以及AOP相关的配置(@Transactional)。随着注解的大量使用,尤其相同的多个注解用到各个类或方法中,会相当繁琐。这就是所谓的样板代码,是spring设计的原则中要消除的代码。

      所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解,组合注解具备注解其上的元注解的功能。spring的很多注解都可以作为元注解,而且spring本身已经有很多组合注解,如@Configuration就是一个组合@Component注解,表明这个类其实也是一个Bean。

      前面我们大量使用了@Configuration和@ComponentScan注解到配置类上,下面我们把这两个注解组成一个组合注解。

     示例:

package jack.ch3.annotation;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.lang.annotation.*;

/**
 * Created by jack on 2017/7/15.
 * 组合注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //组合@Configuration元注解
@ComponentScan //组合@ComponentScan元注解
public @interface WiselyConfiguration {
    //覆盖value参数
    String [] value() default {};
}


演示服务的Bean:

package jack.ch3.annotation;

import org.springframework.stereotype.Service;

/**
 * Created by jack on 2017/7/15.
 */
@Service
public class DemoService {
    public void outputResult(){
        System.out.println("从组合注解配置照样获取的Bean");
    }
}


  配置类:

package jack.ch3.annotation;

/**
 * Created by jack on 2017/7/15.
 */
//使用@WiselyConfiguration组合注解替代@Configuration和@ComponentScan
@WiselyConfiguration("jack.ch3.annotation")
public class DemoConfig {
}


  测试代码如下:

package jack.ch3.annotation;

import jack.ch3.conditional.ConditionConfig;
import jack.ch3.conditional.ListServer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * Created by jack on 2017/7/15.
 */
public class MainTest12 {
    public static void main(String [] args){
        //AnnotationConfigApplicationContext作为spring容器,接受一个配置类作为参数
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
        DemoService demoService = context.getBean(DemoService.class);
        demoService.outputResult();
        context.close();
    }
}


   运行程序,输出如下:

D:\java\jdk1.8.0_111\bin\java -Didea.launcher.port=7535 "-Didea.launcher.bin.path=D:\programmingsoftware\IDEA\JetBrains\IntelliJ IDEA 2016.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\java\jdk1.8.0_111\jre\lib\charsets.jar;D:\java\jdk1.8.0_111\jre\lib\deploy.jar;D:\java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;D:\java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;D:\java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;D:\java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;D:\java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;D:\java\jdk1.8.0_111\jre\lib\ext\localedata.jar;D:\java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;D:\java\jdk1.8.0_111\jre\lib\ext\sunec.jar;D:\java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;D:\java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;D:\java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;D:\java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;D:\java\jdk1.8.0_111\jre\lib\javaws.jar;D:\java\jdk1.8.0_111\jre\lib\jce.jar;D:\java\jdk1.8.0_111\jre\lib\jfr.jar;D:\java\jdk1.8.0_111\jre\lib\jfxswt.jar;D:\java\jdk1.8.0_111\jre\lib\jsse.jar;D:\java\jdk1.8.0_111\jre\lib\management-agent.jar;D:\java\jdk1.8.0_111\jre\lib\plugin.jar;D:\java\jdk1.8.0_111\jre\lib\resources.jar;D:\java\jdk1.8.0_111\jre\lib\rt.jar;E:\webworkspace\IdeaProjects\springstudy1\target\classes;C:\Users\wj\.m2\repository\org\springframework\spring-context\4.1.6.RELEASE\spring-context-4.1.6.RELEASE.jar;C:\Users\wj\.m2\repository\org\springframework\spring-beans\4.1.6.RELEASE\spring-beans-4.1.6.RELEASE.jar;C:\Users\wj\.m2\repository\org\springframework\spring-core\4.1.6.RELEASE\spring-core-4.1.6.RELEASE.jar;C:\Users\wj\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;C:\Users\wj\.m2\repository\org\springframework\spring-expression\4.1.6.RELEASE\spring-expression-4.1.6.RELEASE.jar;C:\Users\wj\.m2\repository\org\springframework\spring-aop\4.1.6.RELEASE\spring-aop-4.1.6.RELEASE.jar;C:\Users\wj\.m2\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;C:\Users\wj\.m2\repository\org\aspectj\aspectjrt\1.8.5\aspectjrt-1.8.5.jar;C:\Users\wj\.m2\repository\org\aspectj\aspectjweaver\1.8.5\aspectjweaver-1.8.5.jar;C:\Users\wj\.m2\repository\commons-io\commons-io\2.3\commons-io-2.3.jar;C:\Users\wj\.m2\repository\javax\annotation\jsr250-api\1.0\jsr250-api-1.0.jar;D:\programmingsoftware\IDEA\JetBrains\IntelliJ IDEA 2016.3.2\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain jack.ch3.annotation.MainTest12
七月 15, 2017 3:27:09 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d099f62: startup date [Sat Jul 15 15:27:09 CST 2017]; root of context hierarchy
从组合注解配置照样获取的Bean
七月 15, 2017 3:27:09 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d099f62: startup date [Sat Jul 15 15:27:09 CST 2017]; root of context hierarchy

Process finished with exit code 0


 

二:@Enable*注解的工作原理

    在前面我们通过@EnableAspectJAutoProxy开启对AspectJ自动代理的支持。

    @EnableAsync开启异步方法的支持

    @EnableScheduling开启计划任务的支持

    @EnableWebMvc开启Web MVC的配置支持

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

    @EnableJpaRepositor开启对Spring Data JPA Repository的支持

    @EnableTransactionManagement开启注解事务的支持

    @EnableCaching开启注解式的缓存支持


        通过简单的@Enable*来开启一项功能的支持,从而避免自己配置大量的代码,大大降低了使用难度。下面看看实现该功能的原理。

       通过观察这些@Enable*注解的源码,我们发现所有的注解都有一个@Import注解,@Import是用来导入配置类的,这也意味着这些自动开启的实现其实是导入一些自动配置的bean。这些导入的配置方式主要分为以下三种类型。

     1,第一类:直接导入配置类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.SchedulingConfiguration;

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

      直接导入配置类SchedulingConfiguration,这个类注解了@Configuration,且注册了一个scheduledAnnotationProcessor的Bean,源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.scheduling.annotation;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;

@Configuration
public class SchedulingConfiguration {
    public SchedulingConfiguration() {
    }

    @Bean(
        name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
    )
    @Role(2)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }
}


   2,第二类:依据条件选择配置类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.scheduling.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.AsyncConfigurationSelector;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    Class<? extends Annotation> annotation() default Annotation.class;

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default 2147483647;
}

AsyncConfigurationSelector通过条件来选择需要导入的配置类,AsyncConfigurationSelector的根接口为ImportSelector,这个接口需重写selectImports方法,在此方法内进行事先条件判断。此例中,若adviceMode为PORXY,则返回ProxyAsyncConfiguration这个配置类;若activeMode为ASPECTJ,则返回AspectJAsyncConfiguration配置类,源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.scheduling.annotation;

import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.ProxyAsyncConfiguration;

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

    public AsyncConfigurationSelector() {
    }

    public String[] selectImports(AdviceMode adviceMode) {
        switch(null.$SwitchMap$org$springframework$context$annotation$AdviceMode[adviceMode.ordinal()]) {
        case 1:
            return new String[]{ProxyAsyncConfiguration.class.getName()};
        case 2:
            return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
        default:
            return null;
        }
    }
}


     3,第三类:动态注册Bean

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AspectJAutoProxyRegistrar;
import org.springframework.context.annotation.Import;

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


    AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistar接口,ImportBeanDefinitionRegistrar的作用是在运行时自动添加Bean到已有的配置类,通过重写方法

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if(enableAJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }

    }


        其中AnnotationMetadata参数用来获得当前配置类上的注解;BeanDefinitionRegistry参数用来注册Bean。源码如下:

     

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.context.annotation;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if(enableAJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }

    }
}


三:测试

        测试是开发中不可缺少的部分,单元测试只针对当前开发的类和方法进行测试,可以简单通过模拟依赖来实现,对运行环境没有依赖。

       spring通过Spring TestContex Framework对集成测试提供顶级支持。它不依赖于特定的测试框架,既可使用Junit,也可以使用TestNG。

       基于Maven构建的项目结构默认有关于测试的目录:src/test/java(测试代码),src/test/resources(测试资源),区别于src/main/java(项目源码),src/main/resources(项目资源)

        Spring提供了一个SpringJUnit4ClassRunner类,它提供了Spring TestContext Framework的功能。通过@ContextConfiguration来配置ApplicationContext,通过@ActiveProfiles确定活动的profile。

        在使用了Spring测试后,我们前面例子的运行部分都可以用spring测试来检验功能能否正常运作。

        下面对简单配置的Application Context和在测试中注入Bean做演示。

        1,准备

        增加Spring测试的依赖包到Maven

<!--Spring test 支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

业务代码:

 在src/main/java下的源码:

package jack.ch3.test;

/**
 * Created by jack on 2017/7/16.
 */
public class TestBean {
    private String content;

    public TestBean(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}


  配置类:

package jack.ch3.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

/**
 * Created by jack on 2017/7/16.
 */
@Configuration
public class TestConfig {
    @Bean
    @Profile("dev")
    public TestBean devTestBean(){
        return new TestBean("from development profile");
    }
    @Bean
    @Profile("prod")
    public TestBean prodTestBean(){
        return new TestBean("from production profile");
    }
}


测试:

  在src/test/java下的代码:

package jack.ch3.test;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by jack on 2017/7/16.
 */
@RunWith(SpringJUnit4ClassRunner.class)//SpringJUnit4ClassRunner在JUnit环境下提供Spring TestContext Framework的功能
@ContextConfiguration(classes = {TestConfig.class}) //@ContextConfiguration用来加载配置ApplicationContext,其中classes属性用来加载配置类
@ActiveProfiles("prod") //@ActiveProfiles用来声明活动的profile
public class DemoBeanIntegrationTests {
    @Autowired //可使用普通的@Autowired注入Bean
    private TestBean testBean;
    @Test //测试代码,通过JUnit的Assert来校验是否和预期一致
    public void prodBeanShouldInject(){
        String expected = "from production profile";
        String actual = testBean.getContent();
        Assert.assertEquals(expected,actual);
    }
}



测试结果如下:



@ActiveProfiles("prod")
改为
@ActiveProfiles("dev")

将采用开发配置的bean


到目前为止,maven中pom.xml的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jack</groupId>
    <artifactId>springstudy1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--定义属性-->
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <!--添加依赖-->
    <dependencies>
        <!--添加spring框架依赖包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>

        <!--spring的aop支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <!--aspectj支持-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.5</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.5</version>
        </dependency>

        <!--增加commons-io可简化文件相关操作-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.3</version>
        </dependency>

        <!--增加JSR250支持-->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
        </dependency>

        <!--Spring test 支持-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

    </dependencies>

    <!--添加插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值