@Recover 方法没有被 Spring AOP 建议拦截 [英] @Recover method not intercepted by Spring AOP advice

查看:55
本文介绍了@Recover 方法没有被 Spring AOP 建议拦截的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用 Spring/Java 和面向方面的编程编写代码时,我遇到了一个问题.在服务类中,我有使用@Retryable 的重试方法和使用@Recover 的恢复方法.

这 2 个方法中的每一个都附加到 Aspects.Retryable 方法 - triggerJob"TestProcessService 内部附加到 TestAspect 类中的这些方法 - beforeTestTriggerJobsAdvice、afterTestTriggerJobsAdvice、onErrorTestTriggerJobsAdvice.它们都运行良好,并且在正确的时间被触发.

问题说明:恢复方法 - 恢复"TestProcessService 内部附加到 TestAspect 类中的这些方法 - beforeRecoveryTestJobsAdvice、onErrorRecoveryTestTriggerJobsAdvice 和 afterRecoveryTestTriggerJobsAdvice.

但是一旦代码到达 TestProcessService 内部的恢复方法,这些方面的方法都不会被调用.

代码如下:

SCHEDULER CLASS(定期触发 TEST_MyProcessService 类中的方法)

@Slf4j@组件公共类 TEST_ScheduledProcessPoller {私人最终 TEST_MyProcessService MyProcessService;私人最终 MyServicesConfiguration MyServicesConfiguration;公共 TEST_ScheduledProcessPoller(TEST_MyProcessService MyProcessService,我的服务配置我的服务配置) {this.MyProcessService = MyProcessService;this.MyServicesConfiguration = MyServicesConfiguration;}@Scheduled(cron = "0 0/2 * * * *")公共无效 scheduleTaskWithFixedDelay() {试试{log.info(scheduleTaskWithFixedDelay");this.triggerMyJobs(true);} 捕获(异常 e){log.error(e.getMessage());}}protected void triggerMyJobs(boolean isDaily) 抛出异常 {log.info(triggerMyJobs");MyServiceType serviceType = this.MyServicesConfiguration.getMy();this.MyProcessService.triggerJob(serviceType, isDaily, 1L);}}

服务等级:

@Slf4j@服务公共类 TEST_MyProcessService {@Retryable(maxAttemptsExpression = "${api.retry.limit}", backoff = @Backoff(delayExpression = "${api.retry.max-interval}"))public void triggerJob(MyServiceType MyServiceType, boolean isDaily, long eventId) {//这里的一些代码可以抛出异常.log.info(triggerJob");throw new RuntimeException(triggerJob");}@恢复public void recovery(MyServiceType MyServiceType, boolean isDaily, long eventId) {log.info(恢复");//这里的一些代码可以抛出异常.抛出新的运行时异常();}}

方面类别:

@Component@Slf4j公共类 TEST_MyAspect {@Pointcut("execution(* packgName.otherProj.services.TEST_MyProcessService.triggerJob(..))")公共无效MyTriggerJobs(){}@Pointcut("execution(* packgName.otherProj.services.TEST_MyProcessService.recover(..))")公共无效 MyRecoverJobs() {}@Before("MyTriggerJobs()")公共无效 beforeMyTriggerJobsAdvice(JoinPoint joinPoint) {log.info(在MyTriggerJobsAdvice之前登录");}@AfterReturning("MyTriggerJobs()")公共无效 afterMyTriggerJobsAdvice(JoinPoint joinPoint) {log.info(在MyTriggerJobsAdvice之后登录");}@AfterThrowing(value = "MyTriggerJobs()", throwing = "error")public void onErrorMyTriggerJobsAdvice(JoinPoint joinPoint, Throwable error) {log.info("log onErrorMyTriggerJobsAdvice");}@Before("MyRecoverJobs()")公共无效 beforeMyRecoverJobsAdvice(JoinPoint joinPoint) {log.info(在MyRecoverJobsAdvice之前登录");}@AfterThrowing(value = "MyRecoverJobs()", throwing = "error")public void onErrorRecoveryMyTriggerJobsAdvice(JoinPoint joinPoint, Throwable error) {log.info("log onErrorRecoveryMyTriggerJobsAdvice");}@AfterReturning("MyRecoverJobs()")公共无效 afterRecoveryMyTriggerJobsAdvice(JoinPoint joinPoint) {log.info("log afterRecoveryMyTriggerJobsAdvice");}}

日志输出:

2021-06-02 20:56:00.016 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 ---[Process schedule-1.Schler.2021-06-02 20:56:00.016 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.ProcessB2ecb0deefc04d6840b62742021-06-02 20:56:00.051 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.BaspectdasspectTrigger.Advice2021-06-02 20:56:00.060 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [调度-1] c.Services.ob2cb0deefc04d6840b62742021-06-02 20:56:00.061 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.Baspict.B.Trigger.2021-06-02 20:56:05.065 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.BaspectdasspectTrigger.Advice2021-06-02 20:56:05.066 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [调度-1] c.services.ob2ecb0deefc04d6840b62742021-06-02 20:56:05.066 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.Baspict.B.Trigger.2021-06-02 20:56:10.070 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.BaspectdasspectTrigger.Advice2021-06-02 20:56:10.070 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [调度-1] c.Services.OBS_trigger2021-06-02 20:56:10.070 INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.c.Baspict.B.Trigger.2021-06-02 20:56:10.070 信息 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [调度-1] c.services.TEST_P.R.2021-06-02 20:56:10.070 错误 [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [scheduling-1] c.Process.TEST_Polled:educ

解决方案

我不是 Spring 用户,但对 AOP 的所有事物都感兴趣,包括 AspectJ 和 Spring AOP.我喜欢你的小拼图.感谢您的


更新:我创建了 Spring Retry 问题 #244 代表您.您想订阅它,以便了解是否/何时修复.

更新 2: 该问题已得到修复,已合并到主分支中,并且很可能会成为即将发布的 1.3.2 版本的一部分.现在,您可以克隆 Spring Retry,自己构建并使用快照.我对您的 MCVE 进行了重新测试,现在恢复方法的方面已按预期启动.

While writing code using Spring/Java and Aspect oriented programing, I'm facing an issue. In the service class, I have the retry method using @Retryable and a recovery method using @Recover.

Each of these 2 methods are attached to Aspects. The Retryable method - "triggerJob" inside TestProcessService is attached to these methods in TestAspect class - beforeTestTriggerJobsAdvice, afterTestTriggerJobsAdvice, onErrorTestTriggerJobsAdvice. They all are working fine and getting triggered at the right time.

PROBLEM STATEMENT: The Recovery method - "recover" inside TestProcessService is attached to these methods in TestAspect class - beforeRecoveryTestJobsAdvice, onErrorRecoveryTestTriggerJobsAdvice, and afterRecoveryTestTriggerJobsAdvice.

BUT NONE OF THESE ASPECT METHODS ARE GETTING CALLED once the code reached the recover method inside TestProcessService.

Here is the code:

SCHEDULER CLASS (triggers the methods inside TEST_MyProcessService class at regular interval)

@Slf4j
@Component
public class TEST_ScheduledProcessPoller {

    private final TEST_MyProcessService MyProcessService;
    private final MyServicesConfiguration MyServicesConfiguration;

    public TEST_ScheduledProcessPoller(TEST_MyProcessService MyProcessService,
                                  MyServicesConfiguration MyServicesConfiguration) {
        this.MyProcessService = MyProcessService;
        this.MyServicesConfiguration = MyServicesConfiguration;
    }

    @Scheduled(cron = "0 0/2 * * * *")
    public void scheduleTaskWithFixedDelay() {
        try {
            log.info("scheduleTaskWithFixedDelay");
            this.triggerMyJobs(true);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    protected void triggerMyJobs(boolean isDaily) throws Exception {
        log.info("triggerMyJobs");
        MyServiceType serviceType = this.MyServicesConfiguration.getMy();
        this.MyProcessService.triggerJob(serviceType, isDaily, 1L);
    }
}

SERVICE CLASS:

@Slf4j
@Service
public class TEST_MyProcessService {

    @Retryable(maxAttemptsExpression = "${api.retry.limit}", backoff = @Backoff(delayExpression = "${api.retry.max-interval}"))
    public void triggerJob(MyServiceType MyServiceType, boolean isDaily, long eventId) {
        // Some code here that can throw exceptions.
        log.info("triggerJob");
        throw new RuntimeException("triggerJob");
    }

    @Recover
    public void recover(MyServiceType MyServiceType, boolean isDaily, long eventId) {
        log.info("recover");
        // Some code here that can throw exceptions.
        throw new RuntimeException();
    }
}

ASPECT CLASS:

@Component
@Slf4j
public class TEST_MyAspect {

    @Pointcut("execution(* packgName.otherProj.services.TEST_MyProcessService.triggerJob(..))")
    public void MyTriggerJobs() {
    }

    @Pointcut("execution(* packgName.otherProj.services.TEST_MyProcessService.recover(..))")
    public void MyRecoverJobs() {
    }

    @Before("MyTriggerJobs()")
    public void beforeMyTriggerJobsAdvice(JoinPoint joinPoint) {
        log.info("log beforeMyTriggerJobsAdvice");
    }

    @AfterReturning("MyTriggerJobs()")
    public void afterMyTriggerJobsAdvice(JoinPoint joinPoint) {
        log.info("log afterMyTriggerJobsAdvice");
    }

    @AfterThrowing(value = "MyTriggerJobs()", throwing = "error")
    public void onErrorMyTriggerJobsAdvice(JoinPoint joinPoint, Throwable error) {
        log.info("log onErrorMyTriggerJobsAdvice");
    }

    @Before("MyRecoverJobs()")
    public void beforeMyRecoverJobsAdvice(JoinPoint joinPoint) {
        log.info("log beforeMyRecoverJobsAdvice");
    }

    @AfterThrowing(value = "MyRecoverJobs()", throwing = "error")
    public void onErrorRecoveryMyTriggerJobsAdvice(JoinPoint joinPoint, Throwable error) {
        log.info("log onErrorRecoveryMyTriggerJobsAdvice");
    }


    @AfterReturning("MyRecoverJobs()")
    public void afterRecoveryMyTriggerJobsAdvice(JoinPoint joinPoint) {
        log.info("log afterRecoveryMyTriggerJobsAdvice");
    }
}

LOG output:

2021-06-02 20:56:00.016  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.c.TEST_ScheduledProcessPoller    : scheduleTaskWithFixedDelay
2021-06-02 20:56:00.016  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.c.TEST_ScheduledProcessPoller    : triggerBdaJobs
2021-06-02 20:56:00.051  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log beforeBdaTriggerJobsAdvice
2021-06-02 20:56:00.060  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.services.TEST_BdaProcessService  : triggerJob
2021-06-02 20:56:00.061  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log onErrorBdaTriggerJobsAdvice
2021-06-02 20:56:05.065  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log beforeBdaTriggerJobsAdvice
2021-06-02 20:56:05.066  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.services.TEST_BdaProcessService  : triggerJob
2021-06-02 20:56:05.066  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log onErrorBdaTriggerJobsAdvice
2021-06-02 20:56:10.070  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log beforeBdaTriggerJobsAdvice
2021-06-02 20:56:10.070  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.services.TEST_BdaProcessService  : triggerJob
2021-06-02 20:56:10.070  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.component.aspect.TEST_BdaAspect  : log onErrorBdaTriggerJobsAdvice
2021-06-02 20:56:10.070  INFO [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.services.TEST_BdaProcessService  : recover
2021-06-02 20:56:10.070 ERROR [,60b7f0602b2ecb0deefc04d6840b6274,eefc04d6840b6274,true] 92605 --- [   scheduling-1] c.c.p.r.c.TEST_ScheduledProcessPoller    : null

解决方案

I am not a Spring user, but interested in all things AOP, both AspectJ and Spring AOP. I liked your little puzzle. Thanks to your MCVE, I was able to reproduce the issue and debug into it. This is a perfect example for why an MCVE is so much superior to simply posting a bunch of code snippets. So thanks for that, please keep up this way of asking questions.

When looking at the situation in a debugger, you see that while the aspect is proceeding into triggerJob, at some point we are in method AnnotationAwareRetryOperationsInterceptor.invoke and there we have the following code:

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
    if (delegate != null) {
        return delegate.invoke(invocation);
    }
    else {
        return invocation.proceed();
    }
}

At this point Spring makes a choice to construct the delegate which later will be used for calling recover. It is constructed with target object invocation.getThis(), which points to the original object, i.e. a TEST_BdaProcessService instance. At this point, the code could simply use invocation.getProxy() instead, which would point to the AOP proxy, i.e. a TEST_BdaProcessService$$EnhancerBySpringCGLIB$$2f8076ac instance. The problem is that the target object reference is passed through to the point where recover gets called, and the corresponding recoverer instance at that point only knows the target object, having no clue of the corresponding proxy object anymore.

When I experimentally assigned the proxy to the delegate as a target, your advice method was invoked.

So we are talking about a Spring limitation here. Whether that was a deliberate choice in order to avoid any other related problems or simply an oversight, I have no idea.


Update: I created Spring Retry issue #244 on your behalf. You want to subscribe to it, so you can find out if/when it is going to be fixed.

Update 2: The issue has been fixed, is merged into the main branch and is probably going to be part of the upcoming 1.3.2 version, whenever that one might be released. For now, you can just clone Spring Retry, build by yourself and use the snapshot. I retested against your MCVE, the aspect now kicks in as expected for the recover method.

这篇关于@Recover 方法没有被 Spring AOP 建议拦截的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆