【问题标题】:Spring AOP: aspect @Around doesn't workSpring AOP:方面@Around 不起作用
【发布时间】:2017-03-11 10:01:36
【问题描述】:

我使用 Spring Boot 和 Spring Initializr 制作了一个简单的 Web 应用程序,并尝试使用 @Around 建议编写 @Aspect

当我将自定义注释 @RetryOnFailure 添加到控制器的端点方法时 - 它可以工作,但是当我将此注释添加到控制器端点执行的控制器方法时 - 它不会工作。我花了很多时间来了解这种行为的原因,但没有任何结果。所以请帮忙。

项目位于:https://github.com/zalizko/spring-aop-playground

@Aspect
@Component
public final class MethodRepeater {

    @Around("execution(* *(..)) && @annotation(RetryOnFailure)")
    public Object wrap(final ProceedingJoinPoint joinPoint) throws Throwable {
        // code is here
    }
}

所以,我的目标是:

@RequestMapping
public String index() {
    inTry();
    return "OK";
}


@RetryOnFailure(attempts = 3, delay = 2, unit = TimeUnit.SECONDS)
public void inTry() {
    throw new RuntimeException("Exception in try " + ++counter);
}

【问题讨论】:

  • inTry() 总是抛出异常。这有意义吗?
  • 只是举例。我有真正的项目,需要的地方。如果某些外部资源不可用,则需要实现“重试”功能。

标签: java aop spring-aop


【解决方案1】:

您犯了一个典型的 Spring AOP 初学者错误:您忘记了基于代理的 AOP 仅在从外部调用代理方法时才有效,而不是通过 this(避免代理)。但内部调用inTry()this.inTry() 相同。因此,切面永远不会触发inTry,您必须像这样重新排列代码:

package spring.aop;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController("/")
public class HomeController {

    static int counter = 0;

    @RequestMapping
    @RetryOnFailure(attempts = 3, delay = 2, unit = TimeUnit.SECONDS)
    public String index() {
        throw new RuntimeException("Exception in try " + ++counter);
    }
}

我也稍微改变了外观,以便

  • 避免反射,直接通过@annotation()将注解绑定到advice参数,
  • 在触发建议时记录连接点并
  • 在尝试 #3 时返回“OK”(只是为了好玩,没有必要)。
package spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public final class MethodRepeater {

    @Around("execution(* spring.aop..*(..)) && @annotation(retryOnFailure)")
    public Object wrap(final ProceedingJoinPoint joinPoint, RetryOnFailure retryOnFailure) throws Throwable {
        System.out.println(joinPoint);
        return proceed(joinPoint, retryOnFailure);
    }

    private Object proceed(ProceedingJoinPoint joinPoint, RetryOnFailure retryOnFailure) throws Throwable {
        int attempt = 1;
        while (true) {
            try {
                return joinPoint.proceed();
            } catch (final Throwable ex) {
                System.out.println("Try #" + attempt + " failed: " + ex);
                if (++attempt >= retryOnFailure.attempts())
                    return "OK";
                if (retryOnFailure.delay() > 0L)
                    retryOnFailure.unit().sleep(retryOnFailure.delay());
            }
        }
    }
}

现在它可以工作了,控制台日志显示:

execution(String spring.aop.HomeController.index())
Try #1 failed: java.lang.RuntimeException: Exception in try 1
Try #2 failed: java.lang.RuntimeException: Exception in try 2

【讨论】:

  • 非常感谢您的解释。你是对的。问题的重点是“为什么?!?”,你描述得很好。干杯!
【解决方案2】:

我遇到了类似的问题,我设法使用 AspectJ 解决了它:

https://github.com/mdanetzky/tour-of-heroes-java

另外 - 我花了一些时间才发现,我的 IDEA 没有正确重建方面,所以在你尝试一些更激烈的措施之前可能值得尝试清理/重建项目。

【讨论】:

  • 那么,你的意思是缺少 aspectj-maven-plugin 的那个问题?
  • 如果我添加 aspectj-maven-plugin 就可以了。非常感谢您的回答,但它是编译时编织,我尝试实现纯 spring-aop - 运行时编织。 docs.spring.io/spring/docs/current/spring-framework-reference/…
  • 不,这不是解决方案。 AspectJ Maven 插件用于成熟的 AspectJ 编译时或后续加载时编织。基于代理的 Spring AOP 绝对没有必要。如果它对您的情况有所帮助,那么只是因为您隐式地从 Spring AOP 切换到 AspectJ,而不是解决问题,而只是避免了真正的问题。
  • 你也可以尝试在你的切入点中写出带有包的注释名称:@Around("execution(* *(..)) && @annotation(com.my.package.RetryOnFailure)" )
  • @MatthiasDanetzky,非常感谢。我决定,由于应用程序设计,我将切换到编译时编织。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-29
  • 1970-01-01
相关资源
最近更新 更多