1Spring Aware
Aware的目的是为了让Bean获得Spring容器的服务。ApplicationContext接口集成了MessageSource接口、ApplicationEventPublisher接口和ResourceLoader接口,所以Bean继承ApplicationContextAware可以获得容器所有服务,但还是要按需继承。
BeanNameAware和ResourceLoaderAware样例:
package com.study.spring.spring5.aware;
import java.io.IOException;
import java.nio.charset.Charset;
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;
/**
*
* @author chaozai
* @date 2018年9月6日
*
*/
@Service
public class AwareService implements BeanNameAware,ResourceLoaderAware{
private String beanName;
private ResourceLoader loader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.loader = resourceLoader;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
/**
* 调用组合对象
*/
public void outputResult(){
System.out.println("Bean Name:"+beanName);
Resource resource = loader.getResource("classpath:config/awareservice.txt");
try {
System.out.println("Resource 获取文件内容:"+IOUtils.toString(resource.getInputStream(),Charset.forName("UTF-8")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
验证:
package com.study.spring.spring5.aware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
*
* @author chaozai
* @date 2018年9月6日
*
*/
@Configuration
@ComponentScan("com.study.spring.spring5.aware")
public class AwareApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AwareApp.class);
AwareService awareService = context.getBean(AwareService.class);
awareService.outputResult();
context.close();
}
}
结果:
Bean Name:awareService
Resource 获取文件内容:hello aware
2多线程
@EnableAsync声明对异步任务的支持
@Async声明方法或者类,声明类则代表该类下所有方法都是异步
线程池配置类:
package com.study.spring.spring5.taskexecutor;
import java.util.concurrent.Executor;
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;
/**
* 任务配置类
* @author chaozai
* @date 2018年9月6日
*
*/
@Configuration
@ComponentScan("com.study.spring.spring5.taskexecutor")
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer{
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);//线程池核心线程数
taskExecutor.setMaxPoolSize(10);//线程池最大线程数
taskExecutor.setQueueCapacity(20);//缓冲(阻塞)队列
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
}
}
任务类:
package com.study.spring.spring5.taskexecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* 任务类
* @author chaozai
* @date 2018年9月6日
*
*/
@Service
public class AsyncTaskService {
@Async
public void executeAsyncTask(int i){
System.out.println("execute async task:"+i);
}
@Async
public void executeOtherAsyncTask(int i){
System.out.println("execute other async task:"+i);
}
}
验证:
package com.study.spring.spring5.taskexecutor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 验证多线程执行任务
* @author chaozai
* @date 2018年9月6日
*
*/
public class TaskExecuteApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskExecutorConfig.class);
AsyncTaskService asyncTaskService = context.getBean(AsyncTaskService.class);
for(int i=0;i<7;i++){
asyncTaskService.executeAsyncTask(i);
asyncTaskService.executeOtherAsyncTask(i);
}
context.close();
}
}
结果:
execute async task:0
execute other async task:2
execute other async task:0
execute other async task:3
execute async task:4
execute other async task:4
execute async task:5
execute other async task:5
execute async task:6
execute other async task:6
execute async task:1
execute other async task:1
execute async task:3
execute async task:2
如果调整核心线程数为1,则会变成同步阻塞运行:
execute async task:0
execute other async task:0
execute async task:1
execute other async task:1
execute async task:2
execute other async task:2
execute async task:3
execute other async task:3
execute async task:4
execute other async task:4
execute async task:5
execute other async task:5
execute async task:6
execute other async task:6
注:因为大家都在阻塞队列中按序等待
如果把同步阻塞队列大小减小为5呢?
队列溢出,将会创建新的任务线程执行,当然前提是正在运行中的总线程数没有超过最大线程数,如果超过了呢?
将使用默认的拒绝策略:AbortPolicy,丢弃任务,并抛出异常:java.util.concurrent.RejectedExecutionException
核心线程池满了-->进入缓冲队列等待-->缓冲队列满了-->创建新的线程直到线程池达到最大线程数-->之后由拒绝策略处理
3计划任务
@EnableScheduling:开启对定时任务支持
@Scheduled:声明方法为计划任务,多种设置方式,如:fixRate(两次开始间隔),fixDelay(上次结束下次开始间隔),cron
定时任务:
package com.study.spring.spring5.taskschedule;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
/**
* 定时任务类
* @author chaozai
* @date 2018年9月6日
*
*/
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss");
/**
* 每隔固定时间执行
*/
@Scheduled(fixedRate=5000)
public void fixedRateTime(){
System.out.println("every five second:"+SIMPLE_DATE_FORMAT.format(new Date()));
}
/**
* 指定时间执行
*/
@Scheduled(cron="0/1 * * * * ?")
public void cronTime(){
System.out.println("every one second:"+SIMPLE_DATE_FORMAT.format(new Date()));
}
}
验证:
package com.study.spring.spring5.taskschedule;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 定时任务演示
* @author chaozai
* @date 2018年9月7日
*
*/
@Configuration
@ComponentScan("com.study.spring.spring5.taskschedule")
@EnableScheduling
public class TaskScheduleApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskScheduleApp.class);
try {
Thread.sleep(7*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
context.close();
}
}
结果:
every five second:23:04:17
every one second:23:04:18
every one second:23:04:19
every one second:23:04:20
every one second:23:04:21
every one second:23:04:22
every five second:23:04:22
every one second:23:04:23
every one second:23:04:24
4条件注解@Conditional
@Conditional根据满足某一个特定条件创建一个特定的Bean
条件对象:
package com.study.spring.spring5.conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Linux大写L
* @author chaozai
* @date 2018年9月7日
*
*/
public class LinuxCondition implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata arg1) {
return context.getEnvironment().getProperty("os.name").contains("Linux");
}
}
package com.study.spring.spring5.conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Windows大写W
* @author chaozai
* @date 2018年9月7日
*
*/
public class WindowsCondition implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata arg1) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
服务对象:
package com.study.spring.spring5.conditional;
/**
* 服务统一接口
* @author chaozai
* @date 2018年9月7日
*
*/
public interface ListService {
public String showListCmd();
}
package com.study.spring.spring5.conditional;
/**
*
* @author chaozai
* @date 2018年9月7日
*
*/
public class LinuxListService implements ListService{
@Override
public String showListCmd() {
return "ls";
}
}
package com.study.spring.spring5.conditional;
/**
*
* @author chaozai
* @date 2018年9月7日
*
*/
public class WindowsListService implements ListService{
@Override
public String showListCmd() {
return "dir";
}
}
验证:
package com.study.spring.spring5.conditional;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* 条件注解验证
* @author chaozai
* @date 2018年9月7日
*
*/
@Configuration
public class ConditionalApp {
@Bean
@Conditional(WindowsCondition.class)
public ListService windowsListService(){
return new WindowsListService();
}
@Bean
@Conditional(LinuxCondition.class)
public ListService linuxListService(){
return new LinuxListService();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionalApp.class);
ListService service = context.getBean(ListService.class);
System.out.println("【"+context.getEnvironment().getProperty("os.name")+"】 list cmd:"+service.showListCmd());
context.close();
}
}
结果:
【Windows 7】 list cmd:dir
5组合注解与元注解
元注解:可以注解到别的注解上的注解
组合注解:被注解的注解,包含一个或多个元注解的功能,类似多继承
之前配置类几乎都包含:@Configuration和@ComponentScan,下面演示将两者合并为一个注解,且功能不变
@Configuration源码:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Component源码:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
@ComponentScan源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
...省略...
}
说明:
@Retention:注解的保留位置
@Target:注解的作用目标
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
@AliasFor:
-
在同一个注解内,对两个不同的属性一起使用,互为别名,且两个属性值必须一致。比如@ComponentScan中basePackages和value。
-
为其他注解的别名,如:@Configuration和@Component中的value
演示:
组合注解定义:
package com.study.spring.spring5.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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
/**
* @Configuration和@ComponentScan组合注解
*
* @author chaozai
* @date 2018年9月7日
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Configuration
@ComponentScan
public @interface SimpleConfiguration {
@AliasFor(annotation = ComponentScan.class)
String[] value() default {};
}
服务样例:
package com.study.spring.spring5.annotation;
import org.springframework.stereotype.Service;
/**
*
* @author chaozai
* @date 2018年9月7日
*
*/
@Service
public class DemoService {
public void print(){
System.out.println("DemoService print");
}
}
验证:
package com.study.spring.spring5.annotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
*
* @author chaozai
* @date 2018年9月7日
*
*/
@SimpleConfiguration
public class AnnotationApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationApp.class);
DemoService service = context.getBean(DemoService.class);
service.print();
context.close();
}
}
结果:
DemoService print
通过自定义注解实现了配置类定义以及Bean的注入。
6@Enable*注解的工作原理
@EnableAspectJAutoProxy开启对AspectJ自动代理的支持
@EnableAsync开启异步方法的支持
@EnableScheduling开启计划任务的支持
@EnableWebMvc开启WebMvc的配置支持
@EnableConfigurationProperties开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories开启对SpringDataJPARepository的支持
@EnableTransactionManagement开启注解式事务的支持
@EnableCaching开启注解式的缓存支持
它们如何实现自动注入对应功能的呢?
每个源码都有一个@import注解,由它来导入配置类。下面来对照着源码进行分析:
6.1直接导入配置类
演示样例:@EnableScheduling
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Import({ SchedulingConfiguration.class })
@Documented
public @interface EnableScheduling {
}
@Configuration
@Role(2)
public class SchedulingConfiguration {
@Bean(name = { "org.springframework.context.annotation.internalScheduledAnnotationProcessor" })
@Role(2)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
public class ScheduledAnnotationBeanPostProcessor
implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered,
EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
...省略...
}
EnableScheduling引入SchedulingConfiguration,SchedulingConfiguration注解了@Configuration,然后通过@Bean注入ScheduledAnnotationBeanPostProcessor,在该类中实现了对定时任务的支持。
6.2依据条件选择配置类
样例:@EnableAsync
@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 Integer.MAX_VALUE;
}
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Nullable
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;
}
}
}
public enum AdviceMode {
PROXY, ASPECTJ;
}
@Configuration
@Role(2)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = { "org.springframework.context.annotation.internalAsyncAnnotationProcessor" })
@Role(2)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
Class customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
if (this.executor != null) {
bpp.setExecutor(this.executor);
}
if (this.exceptionHandler != null) {
bpp.setExceptionHandler(this.exceptionHandler);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(((Integer) this.enableAsync.getNumber("order")).intValue());
return bpp;
}
}
首先引入AsyncConfigurationSelector,然后根据实际AdviceMode选择实际引入的配置类,默认引入ProxyAsyncConfiguration,该配置类通过@Bean注入AsyncAnnotationBeanPostProcessor。
6.3动态注册Bean
样例:@EnableAspectJAutoProxy
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ AspectJAutoProxyRegistrar.class })
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
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);
}
}
}
}
首先引入AspectJAutoProxyRegistrar,该类实现了ImportBeanDefinitionRegistrar接口,它可以在运行时自动添加Bean到已有的配置类。其中AnnotationMetadata用来获取当前配置类上的注解,BeanDefinitionRegistry用来注册Bean。
可以此深入学习AOP通过注解实现的原理。
7测试
Spring通过Spring TestContext Framework对集成测试提供顶级支持,可以使用Junit,也可以使用TestNG。
Spring提供SpringJUnit4ClassRunner类,它提供Spring TestContext Framework功能。通过@ContextConfiguration来配置Application Context,class属性加载具体配置类,通过@ActiveProfiles确定活动的profile。
样例演示:
pom.xml添加依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
测试Bean
package com.study.spring.spring5.test;
/**
*
* @author chaozai
* @date 2018年9月8日
*
*/
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.study.spring.spring5.test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
*
* @author chaozai
* @date 2018年9月8日
*
*/
@Configuration
public class TestConfig {
@Bean
@Profile("dev")
public TestBean devTestBean(){
return new TestBean("development test bean");
}
@Bean
@Profile("prod")
public TestBean prodTestBean(){
return new TestBean("production test bean");
}
}
测试效验:
package com.study.spring.spring5.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;
/**
*
* @author chaozai
* @date 2018年9月8日
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfig.class})
@ActiveProfiles("dev")
public class TestApp {
@Autowired
TestBean bean;
@Test
public void devProductBeanInject(){
String expected = "development test bean";
String actual = bean.getContent();
Assert.assertEquals(expected, actual);
}
@Test
public void prodProductBeanInject(){
String expected = "production test bean";
String actual = bean.getContent();
Assert.assertEquals(expected, actual);
}
}
结果:
因为当前profile环境是:dev,所以第一个成功,第二个失败。
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!