Springboot事件监听与@Async注解详解

 更新时间:2024年01月10日 10:29:57   作者:苦糖果与忍冬  
这篇文章主要介绍了Springboot事件监听与@Async注解详解,在开发中经常可以利用Spring事件监听来实现观察者模式,进行一些非事务性的操作,如记录日志之类的,需要的朋友可以参考下

一、不求甚解

在开发中经常可以利用Spring事件监听来实现观察者模式,进行一些非事务性的操作,如记录日志之类的。

Controller

Controller中注入ApplicationEventPublisher,并利用它发布事件即可。

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/controller")
public class TestController {
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @GetMapping("/test")
    public String test(@RequestParam String name){
        System.out.println("请求进来了");
        TestEvent event = new TestEvent(this,name);
        applicationEventPublisher.publishEvent(event);
        return "success";
    }
}

Event

event继承ApplicationEvent即可。

import org.springframework.context.ApplicationEvent;
public class TestEvent extends ApplicationEvent {
    private String name;
    public TestEvent(Object source,String name) {
        super(source);
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Listener

Listener实现ApplicationListener接口即可。或者使用@EventListener注解

接口方式

import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Async
public class TestListener implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        System.out.println(testEvent.getName());
    }
}

注解方式

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Async
public class TestListener {
    @EventListener
    public void onApplicationEvent(TestEvent testEvent) {
        System.out.println("TestListener:"+Thread.currentThread().getName());
        System.out.println("TestListener:"+testEvent.getName());
    }
}

Postman测试 //localhost:8080/controller/test?name=lisi

在这里插入图片描述

控制台打印:

请求进来了
lisi

曾经,我一直以为这样就实现了异步,因为我看公司的代码都是这样写的,现网也没什么问题。每次照着前辈们的代码CV一下就可以了,也没太多想。

二、人云亦云

@Async注解

以前一直用@Async注解,但我好像没有去深入了解过她。使用她就像使用@Resource一样自然一样随心所欲。

直到某天闲了下来,突发奇想,想要深入了解一下她。

@Async注解很容易踩坑,首先是必须在启动类上开启异步配置@EnableAsync才行,否则还是同步执行。如果不指定线程池,则使用Spring默认的线程池 SimpleAsyncTaskExecutor。

方法上一旦标记了@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

1)在方法上使用该@Async注解,申明该方法是一个异步任务;在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;

2)使用此注解的方法的类对象,必须是Spring管理下的bean对象; 所以需要在类上加上@Component注解。

3)要想使用异步任务,需要在主类上开启异步配置,即,配置上@EnableAsync注解; 

4)如果不指定线程池的名称,则使用Spring默认的线程池SimpleAsyncTaskExecutor。

SimpleAsyncTaskExecutor特性:

  • 1)为每个任务启动一个新线程,异步执行它。
  • 2)支持通过“concurrencyLimit” bean 属性限制并发线程。默认情况下,并发线程数是无限的。
  • 3)注意:此实现不重用线程!
  • 4)考虑一个线程池 TaskExecutor 实现,特别是用于执行大量短期任务。

以上内容是我百度后所得,到底对不对呢?我决定亲自验证一下。

所以,使用@Async注解的时候一定要指定自己的线程池!!!

在启动类没有指定注解@EnableAsync的情况下,测试一下打印出当前的线程

 @GetMapping("/test")
    public String test(@RequestParam String name){
        System.out.println("TestController请求进来了:"+Thread.currentThread().getName());
        TestEvent event = new TestEvent(this,name);
        applicationEventPublisher.publishEvent(event);
        System.out.println("TestController请求出去了:"+Thread.currentThread().getName());
        return "success";
    }
    @Override
    public void onApplicationEvent(TestEvent testEvent) {
        System.out.println("TestListener:"+Thread.currentThread().getName());
        System.out.println("TestListener:"+testEvent.getName());
    }

在这里插入图片描述

根据上述结果可证明在没有开启异步配置@EnableAsync的情况下还是同步执行!

三、刨根问底

Springboot中异步默认使用的线程池真的是SimpleAsyncTaskExecutor吗???

在不指定线程池的情况下,debug查看spring中异步默认的线程池。

开启异步 在启动类上添加该注解 @EnableAsync

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

获取spring管理的bean实例,实现这个接口即可: ApplicationContextAware

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.CustomizableThreadCreator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/controller")
public class TestController implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @GetMapping("/test")
    public String test(@RequestParam String name){
        System.out.println("TestController请求进来了:"+Thread.currentThread().getName());
        TestEvent event = new TestEvent(this,name);
        // 获取spring管理的所有的bean的名称
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (int i = 0; i <beanDefinitionNames.length ; i++) {
            System.out.println(beanDefinitionNames[i]);
        }
        applicationEventPublisher.publishEvent(event);
        System.out.println("TestController请求出去了:"+Thread.currentThread().getName());
        // 根据class对象获取spring管理的bean实例
        CustomizableThreadCreator bean = applicationContext.getBean(CustomizableThreadCreator.class);
        System.out.println("CustomizableThreadCreator:"+bean.getThreadNamePrefix());
        return "success";
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        TestController.applicationContext=applicationContext;
    }
}

在TestListener中打上断点。

在这里插入图片描述

当前spring版本为5.2.12,可能与spring版本有关,当前版本的线程池默认是ThreadPoolTaskExecutor。可以看到线程池最大容量是Integer的最大值,在高并发场景下可能导致OOM。因此需要自定义线程池,并在@Async上指定为自定义线程池。

打印出了spring中所管理的bean的名称,applicationTaskExecutor果然也在。

在这里插入图片描述

打印出线程池的前缀,再一次验证确认线程池默认是ThreadPoolTaskExecutor。

在这里插入图片描述

四、曲突徙薪

最近写了一个接口,要求响应时间1s以内,并且请求量很大,明确要求一些非重要业务的操作都必须异步操作。

按着以前的套路,我还是照着上面一操作了一遍。还好今天研究了一下,否则以后的某天怕是要背一口大锅。

保持好奇,富有探索,始终对技术有敏感性!!!

自定义线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class MyExecutorConfig {
    @Bean("MyExecutor")
    public Executor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(2000);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("myExecutor");
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

在异步注解上标明使用自定义线程池 @Async(“MyExecutor”)

Jmeter压测

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

压测结果表明确实切换了线程池,使用了自定义的线程池,并且阻塞队列已满,开始朝着最大线程数迈进!

到此这篇关于Springboot事件监听与@Async注解详解的文章就介绍到这了,更多相关Springboot监听与@Async内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java高级面试题小结

    Java高级面试题小结

    本文主要介绍了一些在Java面试过程中的一些高级面试题,包括对部分名词的释义及代码解析,具有一定收藏价值,需要的朋友可以参考下
    2017-09-09
  • 浅析Java Scanner 类的用法

    浅析Java Scanner 类的用法

    这篇文章主要介绍了Java Scanner 类的用法,文中讲解非常详细,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页)

    Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页)

    这篇文章主要介绍了Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java Stream流零基础教程

    Java Stream流零基础教程

    Java8的另一大亮点Stream,它与java.io包里的InputStream和OutputStream是完全不同的概念,下面这篇文章主要给大家介绍了关于Java8中Stream详细使用方法的相关资料,需要的朋友可以参考下
    2022-11-11
  • SpringData JPA的常用语法汇总

    SpringData JPA的常用语法汇总

    Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作,下面这篇文章主要给大家介绍了关于SpringData JPA的常用语法,需要的朋友可以参考下
    2022-06-06
  • idea中JRebel不生效问题及解决方案

    idea中JRebel不生效问题及解决方案

    这篇文章主要介绍了idea中JRebel不生效问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • java 直接调用python脚本,并传递参数代码实例

    java 直接调用python脚本,并传递参数代码实例

    这篇文章主要介绍了java调用python脚本传递参数的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • mybatis Map查询结果下划线转驼峰的实例

    mybatis Map查询结果下划线转驼峰的实例

    这篇文章主要介绍了mybatis Map查询结果下划线转驼峰的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java语言中&&与& ||与|的区别是什么

    Java语言中&&与& ||与|的区别是什么

    这篇文章主要介绍了Java语言中&&与& ||与|的区别是什么的相关资料,需要的朋友可以参考下
    2017-04-04
  • java多线程之线程同步七种方式代码示例

    java多线程之线程同步七种方式代码示例

    这篇文章主要介绍了java多线程之线程同步七种方式代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11

最新评论