3.1 Spring Aware
3.1.1 点睛
Spring 的依赖注入最大的亮点就是你所有的Bean对Spring容器的存在是没有意识的.即你可以将你的容器替换成别的容器,如Google Guice,这时Bean之间的耦合度很低.
但是在实际项目中,你不可避免要用到Spring容器本身的功能资源,这时你的Bean必须意识到Spring容器的存在,才能调用Spring所提供的资源,这就是所谓的Spring Aware.其实Spring Aware本来就是Spring设计用来框架内部使用的,若使用了Spring Aware,你的Bean将会和Spring框架耦合.
Spring提供了Aware接口如图所示:
Spring Aware的目的是为了让Bean获得Spring容器的服务.因为ApplicationContext接口集成了MessageSource接口、ApplicationEventPublisher接口和ResourceLoader接口,所以Bean继承ApplicationContextAware可以获得Spring容器的所有服务,但是原则上我们用到什么接口,就实现什么接口.
3.1.2 示例
①:准备一个test.txt,内容随意,给下面的外部资源加载使用
②:Spring Aware演示Bean
package com.example.demo.aware;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
//实现BeanNameAware、ResourceLoaderAware接口,获得Bean名称和资源加载服务
@Service
public class AwareService implements BeanNameAware, ResourceLoaderAware {
private String beanName;
private ResourceLoader loader;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.loader = resourceLoader;
}
public void outputResult() {
System.out.println("Bean的名称为:" + beanName);
Resource resource = loader.getResource("classpath:test.txt");
try {
System.out.println("ResourceLoader加载的文件内容为:"+IOUtils.toString(resource.getInputStream()));
}catch (Exception ex){
System.out.println(ex.getMessage());
}
}
}
③:配置类
package com.example.demo.aware;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AwareConfig {
}
④:运行
package com.example.demo.aware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AwareConfig.class);
AwareService awareService = context.getBean(AwareService.class);
awareService.outputResult();
context.close();
}
}
3.2 多线程
3.2.1 点睛
Spring通过任务执行器TaskExecutor来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.而实际开发中任务一般是非阻碍的,即异步的,所以我们在配置类中通过@EnableAsync开启对异步任务的支持,并通过在实际执行的Bean方法中使用@Async注解来声明其是一个异步任务.
3.2.2 示例
①:配置类
package com.example.demo.taskexecutor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@ComponentScan
@EnableAsync//利用注解开启异步任务支持
public class TaskExecutorConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize();
taskExecutor.setMaxPoolSize();
//队列容量
taskExecutor.setQueueCapacity();
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
②:任务执行类
package com.example.demo.taskexecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncTaskService {
@Async
public void executeAsyncTask(Integer i){
System.out.println("执行异步任务:"+i);
}
@Async
public void executeAsyncTaskPlus(Integer i){
System.out.println("执行异步任务+1"+(i+));
}
}
③:运行
package com.example.demo.taskexecutor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskExecutorConfig.class);
AsyncTaskService asyncTaskService = context.getBean(AsyncTaskService.class);
for(int i=;i<;i++){
asyncTaskService.executeAsyncTask(i);
asyncTaskService.executeAsyncTaskPlus(i);
}
context.close();
}
}
3.3 计划任务
3.3.1 点睛
从Spring3.1开始,计划任务在Spring中的实现变得异常的简单,首先通过配置类注解@EnableScheduling来开启对计划任务的支持,然后在要执行任务的方法上注解@Scheduled,声明这是一个计划任务.
Spring通过@Scheduled支持多种类型的计划任务,包含cron、fixDelay、fixRate等
3.3.2 示例
①:计划任务执行类
package com.example.demo.taskscheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = )
public void reportCurrentTime(){
System.out.println("每隔5秒执行一次"+sdf.format(new Date()));
}
@Scheduled(cron="0 28 11 ? * *")
public void fixTimeExecution(){
System.out.println("在指定时间"+sdf.format(new Date())+"执行");
}
}
②:配置类
package com.example.demo.taskscheduler;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@ComponentScan
@EnableScheduling//通过注解开启对计划任务的支持
public class TaskSchedulerConfig {
}
③:运行
package com.example.demo.taskscheduler;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskSchedulerConfig.class);
}
}
3.4 条件注解@Conditional
3.4.1 点睛
在2.4节,通过活动的profile,我们可以获得不同的Bean.Spring4提供了一个更通用的基于条件的Bean的创建,即使用@Conditional注解
@Conditional根据满足某一个特定的条件创建一个特定的Bean.比如说,当某一个jar包在一个类路径下的时候,自动配置一个或多个Bean;或者只有某个Bean被创建才会创建另外一个Bean.总的来说,是根据特定条件来控制Bean的创建行为,这样我们可以利用这个特性来进行一些自动的配置.
下面展示不同的操作系统作为条件,我们通过实现Condition接口,并重写其matches方法来构造判断条件.若在windows系统下运行程序,则输出列表命令为dir;若在linux操作系统下,则输出列表命令ls.
3.4.1 示例
①:判断条件定义
(1)判定Windows的条件
package com.example.demo.conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().getProperty("os.name").contains("Windows");
}
}
(2)判定Linux的条件
package com.example.demo.conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class LinuxCondition implements Condition{
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().getProperty("os.name").contains("Linux");
}
}
②:不同系统下的Bean的类
(1)接口
package com.example.demo.conditional;
public interface ListServcie {
String showListCmd();
}
(2)Windows下所要创建的Bean的类
package com.example.demo.conditional;
public class WindowsListService implements ListServcie{
@Override
public String showListCmd() {
return "dir";
}
}
(3)Linux下所要创建的Bean的类
package com.example.demo.conditional;
public class LinuxListService implements ListServcie {
@Override
public String showListCmd() {
return "ls";
}
}
③:配置类
@Configuration
public class ConditionConfig {
@Bean
@Conditional(WindowsCondition.class)//通过注解,符合Windows条件实例化WindowsListService
public ListServcie windowsListService(){
return new WindowsListService();
}
@Bean
@Conditional(LinuxCondition.class)//通过注解,符合Linux条件实例化LinuxListService
public ListServcie linuxListService(){
return new LinuxListService();
}
}
④:运行
package com.example.demo.conditional;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
ListServcie listServcie = context.getBean(ListServcie.class);
System.out.println(context.getEnvironment().getProperty("os.name")+"系统下的列表命令为:"+listServcie.showListCmd());
}
}
3.5 组合注解与元注解
3.5.1 点睛
从Spring2开始,为了响应JDK1.5推出的注解功能,Spring开始大量加入注解来替代xml配置.Spring的注解主要用来配置注入Bean,面向相关配置(@Transactional).随着注解的大量使用,尤其相同的注解用到各个类中,会相当啰嗦.这就是所谓的模板代码,是Spring设计原则要消除的代码.
所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解,组合注解具备元注解的功能.Spring的很多注解都可以作为元注解,而且Spring本身已经有很多组合注解,例如@Configuration就是一个组合@Component注解,表明这个类其实也是一个Bean
3.5.2 示例
①:示例组合注解
package com.example.demo.annotation;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration//组合@Configuration 和@ComponentScan 元注解
@ComponentScan
public @interface WiselyConfiguration {
String[] value() default {};//覆盖value参数
}
②:演示服务Bean
package com.example.demo.annotation;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
public void outputResult(){
System.out.println("从组合注解配置照样获得到bean");
}
}
③:新的配置类
package com.example.demo.annotation;
@WiselyConfiguration//使用此注解替代@Configuration和@ComponetScan
public class DemoConfig {
}
④:运行
package com.example.demo.annotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
DemoService demoService = context.getBean(DemoService.class);
demoService.outputResult();
context.close();
}
}
3.6 @Enable*注解的工作原理
观察发现@Enable注解的源码,都有一个@Import注解,@Import是用来导入配置类的,这也意味着这些自动开启的实现其实是导入了一些自动配置的Bean.这些配置方式主要分为以下三种类型.
3.6.1 第一类:直接导入配置类
@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;
@Configuration
@Role()
public class SchedulingConfiguration {
public SchedulingConfiguration() {
}
@Bean(
name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
)
@Role()
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
3.6.2 第二类:根据条件选择配置类(@EnableAsync)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
}
AsyncConfigurationSelector通过条件来选择需要导入的配置类,AsyncConfigurationSelector的根接口为ImportSelector,这个几块需要重写selectImports方法,在此方法内进行事先条件判断,此例中adviceMode为不同的值返回不同的配置类
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
public AsyncConfigurationSelector() {
}
@Nullable
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return new String[]{ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
default:
return null;
}
}
}
3.6.3 第三类:动态注册Bean(@EnableAspectJAutoProxy)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
}
AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar的作用是在运行时自动添加Bean到已有的配置类,通过重写方法
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}
其中AnnotationMetadata 参数用来获得当前配置类上的注解;BeanDefinitionRegistry 参数用来注册Bean
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
AspectJAutoProxyRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
3.7 测试
3.7.1 点睛
Spring通过Spring TestContext Framework对集成测试提供顶级支持.它不依赖于特定的测试框架,即可使用Junit也可使用TestNG.
Spring提供了一个SpringJUnit4ClassRunner类,它提供了Spring TestContext Framework的功能,通过@ContextConfiguration来配置Application Context,通过@ActiveProfiles确定激活的profile
①:准备 增加Spring测试的依赖包到Maven
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
②:业务代码
package com.example.demo.fortest;
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 com.example.demo.fortest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@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");
}
}
④:测试
package com.example.demo;
import com.example.demo.fortest.TestBean;
import com.example.demo.fortest.TestConfig;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfig.class})
@ActiveProfiles("prod")
public class DemoBeanIntegrationTests {
@Autowired
private TestBean testBean;
@Test
public void prodBeanShouldInject(){
String expected ="from production profile";
String actual = testBean.getContent();
Assert.assertEquals(expected,actual);
}
}