背景:之前用线程池处理任务的时候都需要手动写类活着匿名内部类实现Runable接口(或者另外两种,懂得都懂),然后重写run方法,个人感觉比较麻烦。 后来偶然一次接触到 Spring 提供的 @Async 注解,便将两者结合起来使用,发现异常依旧可以直接捕获。 Tip:这里只是一个使用的 demo,作者亲测没什么问题。
1. 配置线程池及参数
我平时用的比较多的是 ThreadPoolTaskExecutor,当然用原声的 ThreadPoolExecutor 也是完全没问题的,这里两种我都测了下,都可以。
package com.edward.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.*;
@Configuration
@EnableAsync
@Slf4j
public class ThreadPoolConfig implements AsyncConfigurer {
@Bean("asyncPoolExecutor")
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setKeepAliveSeconds(60);
executor.setQueueCapacity(2);
executor.setThreadNamePrefix("edward-thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setAllowCoreThreadTimeOut(true);
executor.initialize();
return executor;
}
@Bean("threadPoolExecutor")
public ThreadPoolExecutor threadPoolExecutor() {
BlockingQueue<Runnable> runnables = new LinkedBlockingQueue<>(2);
return new ThreadPoolExecutor(
2,
2,
30,
TimeUnit.SECONDS,
runnables,
new ThreadPoolExecutor.AbortPolicy()
);
}
@Override
public Executor getAsyncExecutor() {
return threadPoolExecutor();
}
}
2. 编写需要线程池处理的业务逻辑
package com.edward.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class AsyncService {
@Async(value = "threadPoolExecutor")
public void testAsync() {
try {
log.info("thrad-name:{},before to do...", Thread.currentThread().getName());
Thread.sleep(2000);
log.info("thrad-name:{},after to do...", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. 调用异步方法
package com.edward.controller;
import com.edward.service.AsyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
@RestController
@RequiredArgsConstructor
@Slf4j
public class AsyncTestController {
private final ThreadPoolExecutor executor;
private final AsyncService asyncService;
@GetMapping(value = "/testAsync")
public String testAsync(){
try {
asyncService.testAsync();
} catch (RejectedExecutionException e) {
log.error("come to RejectedExecutionHandler,{},{}",
executor.getActiveCount(),
executor.getMaximumPoolSize(),e);
} catch (Exception exception) {
log.error("come to Exception",exception);
}
return null;
}
}
4. 注意点:
- 以上三步 controller 中拿到的异常为线程池本身的异常,比如走进了拒绝策略等;
- 以上三步 service 中处理的异常是该方法本身可能出现的异常;
- 关键点:servcie 层中的异常无法在 controller 中捕获!!!。
- 若想在 controller 中捕获 service 层中的异常:
方法一 :老老实实的重写 run 方法,不要用 @Async 注解; 方法二 :可以在 controller 中用 Future 获取异步方法的返回值,然后在 service 层中返回的时候对结果做处理(不过这种处理方式比较麻烦,比重写更麻烦)。
5. 补充(异常获取)
下面重写的方法是在这个类中 public class ThreadPoolConfig implements AsyncConfigurer{}
使用 @Async 注解后,在异步方法中抛出的异常,再重写 AsyncUncaughtExceptionHandler 方法即可捕捉,但无法给前端(作者暂时没找到方法)
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("=========come to handleUncaughtException,", ex);
}
};
}
Tip:这种捕捉方式没有太大的意义,无法让 @ControllerAdvice获取(作者实验下来是这样的,当然如果有办法还请评论区告诉我);不如直接在异步方法中使用 try{…}catch{…} 捕捉。
|