【Spring Boot】三、Spring 高级话题

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:

  1. 在同一个注解内,对两个不同的属性一起使用,互为别名,且两个属性值必须一致。比如@ComponentScan中basePackages和value。

  2. 为其他注解的别名,如:@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,所以第一个成功,第二个失败。

 

 


爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqchaozai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值