1.Spring
1.1 @Async
1.1.1 同步和异步的基本概念
什么是异步任务?
异步任务属于多线程编程的一个环节:主线程在继续当前任务的同时,创建一个或多个新的非阻塞线程去执行其他任务。
异步任务和同步任务的区别
- 同步是阻塞模式,异步是非阻塞模式。
- 同步就是指程序在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会—直等待下去,直到收到返回信息才继续执行下去。
- 异步就是程序调用一个耗时较长的功能(方法)时,它并不会阻塞程序的执行流程,程序会继续往下执行。当功能执行完毕时,程序能够获得执行完毕的消息或能够访问到执行的结果(如果有返回值或需要返回值时)。
1.1.2 @Async注解的基本概念
含义
- 在方法上使用@Async注解,表明该方法是一个异步任务。
- 在类上面使用@Async注解,表明该类中的所有方法都是异步任务。
- 使用此注解的方法的类对象,必须是spring管理下的bean对象(如使用@Component注解将类注册到spring中)。
- 使用异步任务,需要在主启动类上使用@EnableAsync注解开启异步配置。
- @Async注解在使用时,如果不指定线程池的名称,则使用Spring默认的线程池,Spring默认的线程池为SimpleAsyncTaskExecutor。
- 方法上一旦标记了@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。
1.1.3 线程池的使用
使用默认线程池的弊端:并发情况下,会无限创建线程
# spring默认线程池配置如下
默认核心线程数:8
最大线程数:Integet.MAX_VALUE
队列使用LinkedBlockingQueue
容量是:Integet.MAX_VALUE
空闲线程保留时间:60s
线程池拒绝策略:AbortPolicy
1.1.4 SpringBoot中的使用案例
主启动类上使用@EnableAsync注解开启异步配置
@SpringBootApplication
@EnableAsync//该注解也可以加到配置类上
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
异步方法
@Component
public class AscTest {
@Async
public void ascVoidMethod(){
//业务逻辑
System.out.println(Thread.currentThread().getName()+":我是一个异步任务");
}
}
以上就是一个异步方法的简单使用,但此时该异步方法使用的是默认线程池,上面提到使用默认线程池有一些弊端,我们可以根据我们的需求来设置合适的线程池。
自定义线程池
@Configuration
@Data
//@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
/**
* 核心线程
* 当前运行线程小于核心线程数就创建线程
* 当前运行线程大于或等于核心线程数,就把任务加入(阻塞)队列
*/
private int corePoolSize = 10;
/**
* 队列容量
* 当前运行线程大于队列容量,就创建新的线程来执行该任务
*/
private int queueCapacity = 30;
/**
* 最大线程
* 当前运行线程大于最大线程,就通过拒绝策略来拒绝任务
*/
private int maxPoolSize = 50;
/**
* 线程最大空闲时间, 单位:秒
* 当前运行线程大于核心线程或设置allowCoreThreadTimeOut时,
* 线程会根据该属性值进行活性检查,一旦超时就销毁线程
*/
private int keepAliveSeconds = 60;
/**
* 核心线程数是否允许超时
*/
private boolean allowCoreThreadTimeOut = true;
/**
* 线程名称前缀
*/
private String preFix = "async-thread-";
/**
* 拒绝策略
*/
private int rejectedExecutionHandler = 0;
/**
* 配置线程池
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(preFix);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//初始化
executor.initialize();
return executor;
}
/**
* 异步任务中处理异常
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
return new SimpleAsyncUncaughtExceptionHandler();
}
}
写一个接口来测试一下异步方法
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
AscTest ascTest;
@GetMapping
public void testAsyncMethod (){
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+":hello" + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}}).start();
ascTest.ascVoidMethod();
}
}
//打印结果
Thread-17:hello0
async-thread-1:我是一个异步任务
Thread-17:hello1
Thread-17:hello2
Thread-17:hello3
Thread-17:hello4
从打印结果我们可以看到当程序阻塞的时候,异步方法不会被阻塞,会继续执行。
1.1.5 异步监听事件
- @Async相关配置见上一个案例
- 该案例通过AOP,自定义注解和@Async实现根据不同的性别调用不同的事件监听实现类
- 该案例的应用:在本例中我们是通过controller的接口去调用进行测试的,我们还可以在定时任务,mq任务中使用异步监听任务,只要在AOP中通过参数和注解设置调用规则即可。
定义一个事件监听接口
public interface EventListener {
void onEvent(MybatisPlusLearn mybatisPlusLearn);
}
定义一异步任务调用监听事件
@Component
public class AscTest {
@Autowired
private List<EventListener> listListener;
@Async
public void asyncListener(MybatisPlusLearn mybatisPlusLearn){
listListener.forEach(item->
item.onEvent(mybatisPlusLearn)
);
}
}
定义一个自定义注解用于接收性别参数
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SexAnnotation {
boolean sex();
}
定义一个AOP来管理注解接收的参数
@Component
@Aspect
public class AnnotationAop {
/**
* 切入点:拦截注解
*/
@Pointcut("@annotation(com.dong.asc.event.annotation.SexAnnotation)")
private void annotationPointCut() {
}
@Around("annotationPointCut()")
public Object annotationAround(ProceedingJoinPoint jp) throws Throwable {
//获取方法
Method method = ((MethodSignature) jp.getSignature()).getMethod();
// 获取AspectAnnotation注解
SexAnnotation annotation = method.getAnnotation(SexAnnotation.class);
Object[] args = jp.getArgs();
if (args.length==0 || !args[0].getClass().isAssignableFrom(MybatisPlusLearn.class)){
System.out.println("参数不合法");
return null;
}
MybatisPlusLearn mybatisPlusLearn = (MybatisPlusLearn) args[0];
/**
* annotation.sex()可以拿到注解的值
* 本例中该值为boolean,只有两个值true和false
* 如果注解中的字段为string,我们就可以拿到这个string进行判断过滤,如果不符合返回null就不会执行监听事件
*/
boolean flag = annotation.sex();
if (mybatisPlusLearn.getSex()==flag){
//匹配就可以调用方法
return jp.proceed();
}
return null;
}
}
定义两个事件监听接口的实现类
通过上面的AOP我们可以拿到方法的参数和注解的值,通过比较这两个值,我们可以指定当参数性别和注解性别相等才调用接口
@Component
public class ManEventListenerImpl implements EventListener {
//男:true,女:false
@SexAnnotation(sex = true)
@Override
public void onEvent(MybatisPlusLearn mybatisPlusLearn) {
System.out.println("我是"+mybatisPlusLearn.getName()+",我是靓仔");
}
}
@Component
public class WoManListenerImpl implements EventListener {
@SexAnnotation(sex = false)
@Override
public void onEvent(MybatisPlusLearn mybatisPlusLearn) {
System.out.println("我是"+mybatisPlusLearn.getName()+",我是小姐姐");
}
}
定义一个controller调用异步监听方法进行测试
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
AscTest ascTest;
@Autowired
MybatisPlusLearnService mybatisPlusLearnService;
@GetMapping("/listener")
public void asyncListener(){
List<MybatisPlusLearn> list=mybatisPlusLearnService.list();
list.forEach(item-> ascTest.asyncListener(item));
}
}
//测试结果如下
我是小明,我是靓仔
我是小刚,我是靓仔
我是小红,我是小姐姐
我是小王,我是靓仔
1.2 @ConditionalOnProperty
1.2.1 作用
在spring boot中有时候需要控制配置类是否生效,可以使用@ConditionalOnProperty注解来控制@Configuration是否生效
1.2.2 示例
配置文件
city:
company:
logo: dong
配置类
/**
* prefix为配置文件中的前缀,
* name为配置的名字
* havingValue是与配置的值对比值,当两个值相同返回true,配置类生效.
*/
@Configuration
@ConditionalOnProperty(prefix = "city.company",name = "logo",havingValue = "dong")
public class TestConditionalOnProperty {
@Value("${city.company.logo}")
private String logo;
public void test(){
System.out.println(logo);
}
}
测试
@RestController
@RequestMapping("/annotation")
public class AnnotationController {
@Autowired
TestConditionalOnProperty conditionalOnProperty;
@GetMapping("/conditionalOnProperty")
public void testConditionalOnProperty(){
conditionalOnProperty.test();
}
}
当配置文件中city.company.logo的值和配置类中注解havingValue 的值相等,控制台就输出值“dong”,不相等的时候启动程序会直接报错,报错信息如下
Field conditionalOnProperty in com.dong.controller.AnnotationController required a bean of type 'com.dong.annotation.TestConditionalOnProperty' that could not be found.
这是因为当不相等的时候配置类没有生效,就导致@Autowired失败
1.2.3 使用场景
- MQ的消费者,当配置满足相关配置后才能进行消费
@Component
@ConditionalOnProperty(prefix = "city.company", name="logo" , havingValue = "dong")
@RocketMQMessageListener(topic = "one_topic" ,consumerGroup ="one_group",consumeThreadMax = 4)
public class ConsumerOne implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
String msg = new String(messageExt.getBody());
System.out.println(msg);
}
}
1.3 @ConfigurationProperties
1.3.1 作用
- 在spring开发过程中我们常使用到@ConfigurationProperties注解,通常是用来将properties和yml配置文件属性转化为bean对象使用。
- 该注解通过反射为配置类注入值,需要对配置类接收的配置文件属性提供get和set方法。
1.3.2 示例1:将属性转换成bean对象
配置文件
collect:
myMap:
sex: 男
name: 张三
配置类
@Configuration
@ConfigurationProperties(prefix = "collect")
@Data
public class TestConfigurationProperties {
private Map<String,String> myMap =new HashMap<>();
}
可以在配置类中加上@EnableConfigurationProperties(TestConfigurationProperties .class)来使ConfigurationProperties注解生效,@Component和@Configuration有一样的效果
测试类
@RestController
@RequestMapping("/annotation")
public class AnnotationController {
@Autowired
TestConfigurationProperties configurationProperties;
@GetMapping("/ConfigurationProperties")
public void testConfigurationProperties(){
Map<String,String> map= configurationProperties.getMyMap();
System.out.println(map);
//{name=张三, sex=男}
}
}
1.3.3 使用场景
- 像示例1那样初始化集合数据。
- 可以通过prefix 指明前缀,不同字段类型来接收前缀下不同属性的属性值
collect:
sex: 男
name: 张三
@Configuration
@ConfigurationProperties(prefix = "collect")
@Data
public class TestConfigurationProperties {
private String sex;
private String name;
}