Springboot事件监听+@Async注解

一、不求甚解

在开发中经常可以利用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测试
http://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!!!

菜是原罪!!!
在这里插入图片描述

在不指定线程池的情况下,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压测
在这里插入图片描述在这里插入图片描述在这里插入图片描述

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用@Transactional注解可以确保方法在一个事务范围内执行,而@Async注解可以将方法异步执行。在给定的引用中,有几个步骤涉及@Transactional和@Async的使用。 首先,引用中提到了将service1的发布方法改为异步执行,并添加@Async注解,并移除@Transactional注解。这意味着该方法将在一个新的线程中异步执行,并不会参与到外部事务中。 其次,引用中提到了service2的数据备份方法使用了@Async注解来异步执行,这是因为数据备份涉及到多张表和数据量较大,耗时较长。通过异步执行,可以提高整体性能。 然而,引用中指出,虽然在service2的数据备份方法中添加了@Transactional注解,确保了几个备份表的操作和修改状态的操作在同一个事务中执行,但是由于主线程和子线程的事务是相互隔离的,子线程的异常不会影响主线程的事务提交或回滚。 对于优化方案,可以考虑将第四步修改发布状态的操作独立出来,提供一个新的方法,并使用@Transactional注解,并指定propagation = Propagation.REQUIRES_NEW参数。这样,无论前三步是否执行成功,都可以保证修改发布状态的操作在一个新的事务中执行,并且不会受到外部事务的影响。 综上所述,使用@Transactional和@Async注解可以实现方法的事务管理和异步执行。需要根据具体的业务需求来选择合适的注解使用方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值