SpringAOP:如何使用void返回类型在异步方法中重新引发异常
我有以下应用程序(与Gradle+Spring Boot相同的应用程序在这里):SpringAOP:如何使用void返回类型在异步方法中重新引发异常,spring,asynchronous,spring-aop,Spring,Asynchronous,Spring Aop,我有以下应用程序(与Gradle+Spring Boot相同的应用程序在这里): Writer.java包含一些在@Async注释帮助下异步运行的代码。一个方法返回void,另一个方法返回Future。根据文件,允许使用这两种变体 import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import java.util.concurrent.
Writer.java
包含一些在@Async
注释帮助下异步运行的代码。一个方法返回void
,另一个方法返回Future
。根据文件,允许使用这两种变体
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
@Async("customExecutor")
public class Writer {
public void write() {
System.out.println("Writing something");
throw new RuntimeException("Writer exception");
}
public Future<Void> writeFuture() {
System.out.println("Writing something with future");
throw new RuntimeException("Writer exception with future");
}
}
Config.java
支持异步处理+调度。它还按计划调用writer.write
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
@Configuration
@EnableScheduling
@EnableAsync
public class Config {
private final Writer writer;
public Config(Writer writer) {
this.writer = writer;
}
@Scheduled(fixedRate = 1000)
public void writeBySchedule() {
writer.write();
// writer.writeFuture();
}
}
运行此应用程序时,我看到以下输出:
Writing something
2020-07-14 21:16:33.791 ERROR 19860 --- [pool-1-thread-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method: public void com.example.demo.Writer.write()
java.lang.RuntimeException: Writer exception
at com.example.demo.Writer.write(Writer.java:14) ~[main/:na]
at com.example.demo.Writer$$FastClassBySpringCGLIB$$cd00988d.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_242]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_242]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_242]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_242]
...
后者是我试图通过ErrorHandlingThreadPoolExecutor
实现的。但是,我想让我的方法返回void
。
我发现我的异常未到达customErrorHandlingThreadPoolExecutor.handleError()
方法的原因如下:。这个方法是在我的自定义方法之前执行的,似乎没有办法为void
方法重新抛出异常。我知道AsyncConfigurerSupport
类允许自定义异常处理,但异常仍然无法从AsyncExecutionAspectSupport.handleError()中逃逸
总之,如果我的异常将void
声明为返回类型,是否有任何方法可以将我的异常从异步执行的方法传播到ErrorHandlingThreadPoolExecutor.handleError()
?目前看来,我可以不使用@Async
而直接使用执行器,但使用@Async
是否可能?如果不是,那么“侵入性”较小的修复(更改和维护的代码更少)是什么?我有很多异步方法返回void
更新:根据公认的答案,我提出了以下方面:
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
@Component
@Aspect
public class ErrorHandlingAspect implements ApplicationListener<ContextRefreshedEvent> {
public static final String DEFAULT_EXECUTOR_BEAN_NAME = "defaultExecutor";
private Map<String, ErrorHandlingThreadPoolExecutor> errorHandlingExecutors;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// initializing here because not all beans come if initialized in constructor
this.errorHandlingExecutors = event.getApplicationContext()
.getBeansOfType(ErrorHandlingThreadPoolExecutor.class);
}
@Pointcut(
// where @Async is on class level
"@within(org.springframework.scheduling.annotation.Async)"
// where @Async is on method level
+ " || @annotation(org.springframework.scheduling.annotation.Async)")
public void asyncMethods() {
}
@Around("asyncMethods()")
public Object runWithErrorHandling(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Async annotation = method.getAnnotation(Async.class);
if (annotation == null) {
annotation = method.getDeclaringClass().getAnnotation(Async.class);
}
if (annotation == null) {
// shouldn't happen because of pointcut configuration, just for safety
return joinPoint.proceed();
}
String asyncExecutorName = annotation.value();
if (StringUtils.isEmpty(asyncExecutorName)) {
asyncExecutorName = DEFAULT_EXECUTOR_BEAN_NAME;
}
ErrorHandlingThreadPoolExecutor asyncExecutor = errorHandlingExecutors.get(asyncExecutorName);
if (asyncExecutor == null) {
// can happen if the declared executor isn't extending ErrorHandlingThreadPoolExecutor
// or if @Async uses the default executor which is either not registered as a bean at all
// or not named DEFAULT_EXECUTOR_BEAN_NAME
return joinPoint.proceed();
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
asyncExecutor.handleError(throwable);
return null;
}
}
}
import org.apache.commons.lang3.StringUtils;
导入org.aspectj.lang.ProceedingJoinPoint;
导入org.aspectj.lang.annotation.Around;
导入org.aspectj.lang.annotation.Aspect;
导入org.aspectj.lang.annotation.Pointcut;
导入org.aspectj.lang.reflect.MethodSignature;
导入org.springframework.context.ApplicationListener;
导入org.springframework.context.event.ContextRefreshedEvent;
导入org.springframework.scheduling.annotation.Async;
导入org.springframework.stereotype.Component;
导入java.lang.reflect.Method;
导入java.util.Map;
@组成部分
@面貌
公共类ErrorHandlingAspect实现ApplicationListener{
公共静态最终字符串DEFAULT\u EXECUTOR\u BEAN\u NAME=“defaultExecutor”;
私有映射执行器;
@凌驾
ApplicationEvent(ContextRefreshedEvent事件)上的公共无效{
//在这里初始化,因为不是所有bean都是在构造函数中初始化的
this.errorHandlingExecutors=event.getApplicationContext()
.getBeansOfType(ErrorHandlingThreadPoolExecutor.class);
}
@切入点(
//其中@Async位于类级别
“@within(org.springframework.scheduling.annotation.Async)”
//其中@Async位于方法级别
+“| |@annotation(org.springframework.scheduling.annotation.Async)”)
公共方法(){
}
@关于(“asyncMethods()”)
公共对象runWithErrorHandling(ProceedingJoinPoint joinPoint)抛出可丢弃的{
方法Method=((MethodSignature)joinPoint.getSignature()).getMethod();
Async annotation=method.getAnnotation(Async.class);
if(注释==null){
annotation=method.getDeclaringClass().getAnnotation(Async.class);
}
if(注释==null){
//不应该因为切入点配置而发生,只是为了安全
返回joinPoint.procedure();
}
字符串asyncExecutorName=annotation.value();
if(StringUtils.isEmpty(asyncExecutorName)){
asyncExecutorName=默认的\u执行器\u BEAN \u名称;
}
ErrorHandlingThreadPoolExecutor asyncExecutor=errorHandlingExecutors.get(asyncExecutorName);
if(异步执行器==null){
//如果声明的执行器未扩展ErrorHandlingThreadPoolExecutor,则可能发生此情况
//或者@Async使用默认的执行器,该执行器或者根本没有注册为bean
//或未命名为DEFAULT\u EXECUTOR\u BEAN\u NAME
返回joinPoint.procedure();
}
试一试{
返回joinPoint.procedure();
}捕捉(可抛可抛){
asyncExecutor.handleError(可丢弃);
返回null;
}
}
}
优点:
允许在不处理线程的情况下处理异步执行代码中的错误
可以根据执行者进行不同的错误处理
可以包装同时返回void
和Future
的方法
缺点:
无法处理调用线程中的错误(仅在被调用线程中)
需要将默认执行器注册为bean并为其指定一个特定名称
仅适用于@Async
注释,而不适用于使用submit()
直接传递给执行器的异步代码
如果您使用这样的方面,您可以在executor中去掉错误处理块,或者只使用普通的executor并完全删除整个(不真正起作用的)错误处理executor。我做到了,而且效果很好:
package com.example.demo;
导入org.aspectj.lang.ProceedingJoinPoint;
导入org.aspectj.lang.annotation.Around;
导入org.aspectj.lang.annotation.Aspect;
导入org.springframework.stereotype.Component;
@组成部分
@面貌
公共类错误处理方面{
//如有必要,在此处进一步缩小切入点
@大约(“@within(org.springframework.scheduling.annotation.Async)”)
公共对象建议(处理连接点连接点){
试一试{
返回joinPoint.procedure();
}
捕捉(可抛可抛){
手柄错误(可丢弃);
//也可以
Writing something with future
[ERROR] java.lang.RuntimeException: Writer exception with future
...
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
@Component
@Aspect
public class ErrorHandlingAspect implements ApplicationListener<ContextRefreshedEvent> {
public static final String DEFAULT_EXECUTOR_BEAN_NAME = "defaultExecutor";
private Map<String, ErrorHandlingThreadPoolExecutor> errorHandlingExecutors;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// initializing here because not all beans come if initialized in constructor
this.errorHandlingExecutors = event.getApplicationContext()
.getBeansOfType(ErrorHandlingThreadPoolExecutor.class);
}
@Pointcut(
// where @Async is on class level
"@within(org.springframework.scheduling.annotation.Async)"
// where @Async is on method level
+ " || @annotation(org.springframework.scheduling.annotation.Async)")
public void asyncMethods() {
}
@Around("asyncMethods()")
public Object runWithErrorHandling(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Async annotation = method.getAnnotation(Async.class);
if (annotation == null) {
annotation = method.getDeclaringClass().getAnnotation(Async.class);
}
if (annotation == null) {
// shouldn't happen because of pointcut configuration, just for safety
return joinPoint.proceed();
}
String asyncExecutorName = annotation.value();
if (StringUtils.isEmpty(asyncExecutorName)) {
asyncExecutorName = DEFAULT_EXECUTOR_BEAN_NAME;
}
ErrorHandlingThreadPoolExecutor asyncExecutor = errorHandlingExecutors.get(asyncExecutorName);
if (asyncExecutor == null) {
// can happen if the declared executor isn't extending ErrorHandlingThreadPoolExecutor
// or if @Async uses the default executor which is either not registered as a bean at all
// or not named DEFAULT_EXECUTOR_BEAN_NAME
return joinPoint.proceed();
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
asyncExecutor.handleError(throwable);
return null;
}
}
}