一:组合注解与元注解
从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>