Spring事件之异步线程执行

Spring 不仅为我们提供了IOC , AOP功能外,还在这个基础上提供了许多的功能,我们用的最多的可能就是Spring MVC了吧,但是让我们来看下spring-context包,其中包含了缓存、调度、校验功能等等

image.png

这里主要想介绍一下Spring提供的观察者模式实现(事件发布监听)及异步方法执行,这些功能也都是基于AOP实现的

Spring 事件

观察者模式大家都了解,它可以解耦各个功能,但是自己实现的话比较麻烦,Spring为我们提供了一种事件发布机制,可以按需要发布事件,之后由监听此事件的类或方法来执行各自对应的功能,代码互相不影响,以后修改订单后续的逻辑时不会影响到订单创建,有点类似于使用MQ的感觉~

比如在配置中心apollo项目中,在portal创建了app后会发送app创建事件,监听此事件的逻辑处将此消息同步到各个环境的admin sevice中,大家有兴趣可以看下相关代码

现在我们来看看具体如何使用:假设一个下单场景,订单创建成功后可能有一些后续逻辑要处理,但是和创建订单本身没有关系,此时就可以在创建订单完成后,发送一个消息,又相应部分的代码进行监听处理,避免代码耦合到一起

首先创建对应的事件

import org.springframework.context.ApplicationEvent;

public class CreatedOrderEvent extends ApplicationEvent {
	
    private final String orderSn;
    
    public CreatedOrderEvent(Object source, String orderSn) {
    	super(source);
        this.orderSn = orderSn;
    }
    
    public String getOrderSn() {
    	return this.orderSn;
    }
}

现在还需要一个事件发布者和监听者,创建一下

发布

import org.springframework.context.ApplicationEventPublisher;

private ApplicationEventPublisher applicationEventPublisher;

applicationEventPublisher.publishEvent(new CreatedOrderEvent(this, orderSn));

监听的多种实现

1:注解实现  @EventListener

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OrderEventListener {
    
    @EventListener
    public void orderEventListener(CreatedOrderEvent event) {
    	System.out.println("listen orderSn:" + orderCreatedEvent.getOrderSn() + " created");
        //todo 其他订单后处理,如通知其他系统等
    }
}

2:代码实现

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;

@Slf4j
@Component
public class OrderEventListener implements ApplicationListener<CreatedOrderEvent> {
    
    @Override
    public void onApplicationEvent(CreatedOrderEvent event) {
    	System.out.println("listen orderSn:" + orderCreatedEvent.getOrderSn() + " created");
        //todo 其他订单后处理,如通知其他系统等
    }
}

因为这里只是一个简单的例子,所以就用配置文件+main方法来执行了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.demo.spring" />

</beans>
public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        OrderService orderService = applicationContext.getBean(OrderService.class);

        orderService.createOrder("test order");

        applicationContext.close();
    }
}

这时我们看下控制台,会打印出如下内容,证明监听者确实收到了消息

listen orderSn:test order created

简单的事件发布就完成了,其中的其他复杂逻辑由Spring替我们处理了

这里我们要注意一点:发布和监听后处理的逻辑是在一个线程中执行的,不是异步执行

异步方法

有时候我们为了提高响应速度,有些方法可以异步去执行,一般情况下我们可能是手动将方法调用提交到线程池中去执行,但是Spring 为我们提供了简化的写法,在开启了异步情况下,不用修改代码,只使用注解即可完成此功能

这时只需要在要异步执行的方法上添加@Async注解即可异步执行;@EnableAsync 启动异步线程, 如

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@EnableAsync
public class OrderEventListener {
    @Async
    @EventListener
    public void orderEventListener(CreatedOrderEvent event) {
    	
    }
}

在使用@Async会有一些问题建议看各位看下相关文档及源码

我们通过Spring事件同步线程改为异步线程,默认的线程池是不复用线程

我觉得这是这个注解最坑的地方,没有之一!我们来看看它默认使用的线程池是哪个,在前文的源码分析中,我们可以看到决定要使用线程池的方法是org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor。其源码如下:

protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
    AsyncTaskExecutor executor = this.executors.get(method);
    if (executor == null) {
        Executor targetExecutor;
        // 可以在@Async注解中配置线程池的名字
        String qualifier = getExecutorQualifier(method);
        if (StringUtils.hasLength(qualifier)) {
            targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
        }
        else {
            // 获取默认的线程池
            targetExecutor = this.defaultExecutor.get();
        }
        if (targetExecutor == null) {
            return null;
        }
        executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
                    (AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
        this.executors.put(method, executor);
    }
    return executor;
}

最终会调用到org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor这个方法中

protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
   Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
   return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}

可以看到,它默认使用的线程池是SimpleAsyncTaskExecutor。我们不看这个类的源码,只看它上面的文档注释,如下:

image-20200720160047340

主要说了三点

  1. 为每个任务新起一个线程
  2. 默认线程数不做限制
  3. 不复用线程

就这三点,你还敢用吗?只要你的任务耗时长一点,说不定服务器就给你来个OOM

解决方案

最好的办法就是使用自定义的线程池,主要有这么几种配置方法

1.在之前的源码分析中,我们可以知道,可以通过AsyncConfigurer来配置使用的线程池

如下:

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步线程池配置
 */
@Slf4j
@Component
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        MdcThreadPoolTaskExecutor executor = new MdcThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(200);
        executor.setKeepAliveSeconds(5 * 60);
        executor.setQueueCapacity(1000);
        // 自定义实现拒绝策略
        executor.setRejectedExecutionHandler((Runnable runnable, ThreadPoolExecutor exe) -> log.error("当前任务线程池队列已满."));
        // 或者选择已经定义好的其中一种拒绝策略
        // 丢弃任务并抛出RejectedExecutionException异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 丢弃任务,但是不抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        // 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 由调用线程处理该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        // 线程名称前缀
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> log.error("线程池执行任务发生未知异常.", ex);
    }

    /**
     * 增加日志MDC
     */
	public static class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

        /**
         * Gets context for task *
         *
         * @return context for task
         */
        private Map<String, String> getContextForTask() {
            return MDC.getCopyOfContextMap();
        }

        /**
         * All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code execute()} etc.)
         * all delegate to this.
         */
        @Override
        public void execute(@NonNull Runnable command) {
            super.execute(wrap(command, getContextForTask()));
        }

        /**
         * All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
         * all delegate to this.
         */
        @NonNull
        @Override
        public Future<?> submit(@NonNull Runnable task) {
            return super.submit(wrap(task, getContextForTask()));
        }

        /**
         * All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
         * all delegate to this.
         */
        @NonNull
        @Override
        public <T> Future<T> submit(@NonNull Callable<T> task) {
            return super.submit(wrap(task, getContextForTask()));
        }

        /**
         * Wrap callable
         *
         * @param <T>     parameter
         * @param task    task
         * @param context context
         * @return the callable
         */
        private <T> Callable<T> wrap(final Callable<T> task, final Map<String, String> context) {
            return () -> {
                Map<String, String> previous = MDC.getCopyOfContextMap();
                if (context == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(context);
                }
                try {
                    return task.call();
                } finally {
                    if (previous == null) {
                        MDC.clear();
                    } else {
                        MDC.setContextMap(previous);
                    }
                }
            };
        }

        /**
         * Wrap runnable
         *
         * @param runnable runnable
         * @param context  context
         * @return the runnable
         */
        private Runnable wrap(final Runnable runnable, final Map<String, String> context) {
            return () -> {
                Map<String, String> previous = MDC.getCopyOfContextMap();
                if (context == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(context);
                }
                try {
                    runnable.run();
                } finally {
                    if (previous == null) {
                        MDC.clear();
                    } else {
                        MDC.setContextMap(previous);
                    }
                }
            };
        }
    }

}

该方式实现线程的复用以及,子线程继承父线程全链路traceId,方便定位问题

2.直接在@Async注解中配置要使用的线程池的名称

@Async(value = "自定义线程名")
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 中,我们可以通过使用 ApplicationEventPublisher 接口的 publishEvent() 方法来发布事件。默认情况下,该方法是同步执行的,即事件发布后会等待所有监听器处理完事件后才会返回。 如果我们想要异步执行事件处理,可以使用 @Async 注解来实现。具体步骤如下: 1.在配置类上添加 @EnableAsync 注解启用异步执行。 2.在对应的事件监听器方法上添加 @Async 注解。 这样,当事件发布时,对应的监听器方法会在一个新线程异步执行,从而不会阻塞主线程执行。 示例代码如下: ```java @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } } @Component public class MyEventListener { @Async @EventListener public void handleEvent(MyEvent event) { // 处理事件 } } @Component public class MyEventPublisher { @Autowired private ApplicationEventPublisher publisher; public void publishEvent(MyEvent event) { publisher.publishEvent(event); } } ``` 在上面的示例代码中,我们先定义了一个 AsyncConfig 配置类,通过实现 AsyncConfigurer 接口来配置线程池等参数。然后在 MyEventListener 类中,我们使用 @Async 注解来标识 handleEvent() 方法是一个异步方法。最后,在 MyEventPublisher 类中,我们调用 ApplicationEventPublisher 的 publishEvent() 方法来发布事件。 这样,当我们调用 MyEventPublisher 的 publishEvent() 方法时,MyEventListener 中对应的 handleEvent() 方法就会在一个新线程异步执行,不会阻塞主线程执行

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值