SpringAOP:如何使用void返回类型在异步方法中重新引发异常

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.

我有以下应用程序(与Gradle+Spring Boot相同的应用程序在这里):

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
。 我发现我的异常未到达custom
ErrorHandlingThreadPoolExecutor.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;
            }
        }
    }