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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 你知道怎么从Python角度学习Java基础

    你知道怎么从Python角度学习Java基础

    这篇文章主要为大家详细介绍了Python角度学习Java基础的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • 一文秒懂java到底是值传递还是引用传递

    一文秒懂java到底是值传递还是引用传递

    这篇文章主要介绍了java到底是值传递还是引用传递的相关知识,本文通过几个例子给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Java利用Picocli开发一个简化命令行工具

    Java利用Picocli开发一个简化命令行工具

    Picocli 是一个强大、易用且功能丰富的 Java 库,用于开发命令行工具,本文我们就来为大家介绍一下Java如何利用Picocli进行命令行简化功能的吧
    2025-03-03
  • 详解如何保护SpringBoot配置文件中的敏感信息

    详解如何保护SpringBoot配置文件中的敏感信息

    使用过SpringBoot配置文件的朋友都知道,资源文件中的内容通常情况下是明文显示,安全性就比较低一些,所以为了提高安全性,就需要对配置文件中的敏感信息进行保护,下面就为大家介绍一下实现方法吧
    2023-07-07
  • Spring数据库连接池实现原理深入刨析

    Spring数据库连接池实现原理深入刨析

    开发web项目,我们肯定会和数据库打交道,因此就会涉及到数据库链接的问题。在以前我们开发传统的SSM结构的项目时进行数据库链接都是通过JDBC进行数据链接,我们每和数据库打一次交道都需要先获取一次链接,操作完后再关闭链接,这样子效率很低,因此就出现了连接池
    2022-11-11
  • MultipartFile中transferTo(File file)的路径问题及解决

    MultipartFile中transferTo(File file)的路径问题及解决

    这篇文章主要介绍了MultipartFile中transferTo(File file)的路径问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Springboot项目对数据库用户名密码实现加密过程解析

    Springboot项目对数据库用户名密码实现加密过程解析

    这篇文章主要介绍了Springboot项目对数据库用户名密码实现加密过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • java实现账号登录时发送邮件通知

    java实现账号登录时发送邮件通知

    这篇文章主要为大家详细介绍了java如何实现在账号登录时发送邮件通知的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-09-09
  • springboot3.X版本集成mybatis遇到的问题及解决

    springboot3.X版本集成mybatis遇到的问题及解决

    在将SpringBoot3.X版本与MyBatis集成时,直接参考基于SpringBoot2.X的配置方法会导致各种报错,尤其是无法注入mapper的bean问题,这主要是因为SpringBoot3.X版本需要搭配MyBatis3.0.3及以上版本才能正常工作,通过更新maven配置至MyBatis3.0.3版本,可以解决这一问题
    2024-09-09
  • 详解Java分布式IP限流和防止恶意IP攻击方案

    详解Java分布式IP限流和防止恶意IP攻击方案

    这篇文章主要介绍了详解Java分布式IP限流和防止恶意IP攻击方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论