如何正确使用 Spring AOP 来 select 执行带有特定注解的方法?

How to correctly use Spring AOP to select the execution of a method annotated with a specific annotation?

我正在学习 Spring AOP 并且我有以下关于我学习中发现的问题的疑问 material.

因此请考虑以下切入点:execution(@com.myapp.MyCustomAnnotation void *(..))。具体是什么意思?

它给了我以下答案,在我看来有点奇怪(使用我对 AOP 工作原理的了解)。它说:

This will select joint points representing voiud method that are annotated by @com.myapp.MyCustomAnnotation annotation.

好的,那么使用 AOP 意味着什么,我可以指定何时执行一个带有特定注释的特定方法?是对的还是我遗漏了什么?

所以,如果前面的断言是正确的,这意味着(例如)我还可以指定类似的内容:"when it is performed a controller method annotated by @RequestMapping("/listAccounts")?(这意味着,当控制器处理对 /listAccounts 资源的 HttpRequest,执行如下操作:

execution(@RequestMapping("/listAccounts") * *(..))

我能不能做这样的事情?

Spring 正在使用 AspectJ 切入点表达式语言 (https://eclipse.org/aspectj/doc/next/adk15notebook/annotations-pointcuts-and-advice.html)

应用切入点表达式意味着拦截所有具有 'void' return 类型的名称和参数列表的方法调用,只要该方法用 @com.myapp.MyCustomAnnotation 注释。

尽管无法使用注释参数匹配连接点,因此您的第二个切入点表达式无效。

您不能像那样在切入点中显式指定注释的参数。相反,您可以设置一个切入点来捕获所有带有 @RequestMapping 注释的方法,然后从该方法中检索注释并检查该值是否与端点匹配。例如:

@PointCut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}

@Before("requestMapping()")
public void handleRequestMapping(JoinPoint joinPoint) {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    String mapping = method.getAnnotation(RequestMapping.class).value()[0];

    if (mapping.equals("/listAccounts") {
        // do something
    }
}

André R. 的回答是不正确的,因为 可以将注释匹配限制为参数值,但有一个限制:它只适用于简单类型,如字符串,整数和 类,而不是字符串数组。但在您的具体示例中,Spring 注释 @RequestMapping 的值类型是 String[],请参阅 Javadoc。让我们假设您遇到这种情况:

译注:

package de.scrum_master.app;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    int value();
    String name() default "";
    Class<?> type() default Object.class;
}

驱动申请:

这里我们有一个应用程序,其中包含多个方法,这些方法由 Spring 注释 @RequestMapping 和我们自制的注释 @MyAnnotation:

注释
package de.scrum_master.app;

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

public class Application {
    @RequestMapping("/listAccounts")
    @MyAnnotation(11)
    public void doSomething() {}

    public void doSomethingElse() {}

    @RequestMapping(value = {"/listAccounts","/another/method"}, name = "Newton")
    @MyAnnotation(value = 22, type = String.class)
    public void foo() {}

    @RequestMapping(value = "/listAccounts", method = RequestMethod.POST, name = "Einstein")
    @MyAnnotation(value = 11, name = "John Doe", type = String.class)
    public void bar() {}

    @RequestMapping(value = "/listCustomers", method = RequestMethod.GET, name = "Einstein")
    @MyAnnotation(value = 22, name = "Jane Doe")
    public void zot() {}

    @RequestMapping(value = "/listAccounts", produces = {"application/json","application/xml"}, consumes = "text/html", name = "Newton")
    public void baz() {}

    public static void main(String[] args) {
        Application application = new Application();
        application.doSomething();
        application.doSomethingElse();
        application.foo();
        application.bar();
        application.zot();
        application.baz();
    }
}

注解匹配方面:

package de.scrum_master.aspect;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.web.bind.annotation.RequestMapping;

import de.scrum_master.app.MyAnnotation;

@Aspect
public class AnnotationParameterMatcher {
    // Match *all* methods annotated by @RequestMapping
    @Before("execution(* *(..)) && @annotation(requestMapping)")
    public void logRequestMapping(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
        // Print joinpoint and annotation
        System.out.println(thisJoinPoint + " -> " + requestMapping);
    }

    // Match *all* methods annotated by @RequestMapping
    @Before("execution(* *(..)) && @annotation(requestMapping)")
    public void logMatchingValue(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
        // Only print if value array contains "/listAccounts"
        if (Arrays.asList(requestMapping.value()).contains("/listAccounts"))
            System.out.println("  value contains '/listAccounts'");
    }

    // Match *all* methods annotated by @RequestMapping and bind 'name' parameter
    @Before("execution(* *(..)) && @annotation(RequestMapping(name))")
    public void logName(JoinPoint thisJoinPoint, String name) {
        System.out.println("  name = '" + name + "'");
    }

    // Match methods annotated by @MyAnnotation with value=11
    @Before("execution(@MyAnnotation(value=11) * *(..))")
    public void logName(JoinPoint thisJoinPoint) {
        System.out.println("  @MyAnnotation(value=11) detected");
    }

    // Match methods annotated by @MyAnnotation with 3 specific parameter values 
    @Before("execution(@MyAnnotation(value=11, name=\"John Doe\", type=String.class) * *(..)) && @annotation(myAnnotation)")
    public void logName(JoinPoint thisJoinPoint, MyAnnotation myAnnotation) {
        System.out.println("  " + myAnnotation);
    }
}

控制台输出:

execution(void de.scrum_master.app.Application.doSomething()) -> @org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=, produces=[], params=[], value=[/listAccounts], consumes=[])
  value contains '/listAccounts'
  name = ''
  @MyAnnotation(value=11) detected
execution(void de.scrum_master.app.Application.foo()) -> @org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=Newton, produces=[], params=[], value=[/listAccounts, /another/method], consumes=[])
  value contains '/listAccounts'
  name = 'Newton'
execution(void de.scrum_master.app.Application.bar()) -> @org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[POST], name=Einstein, produces=[], params=[], value=[/listAccounts], consumes=[])
  value contains '/listAccounts'
  name = 'Einstein'
  @MyAnnotation(value=11) detected
  @de.scrum_master.app.MyAnnotation(name=John Doe, type=class java.lang.String, value=11)
execution(void de.scrum_master.app.Application.zot()) -> @org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[GET], name=Einstein, produces=[], params=[], value=[/listCustomers], consumes=[])
  name = 'Einstein'
execution(void de.scrum_master.app.Application.baz()) -> @org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=Newton, produces=[application/json, application/xml], params=[], value=[/listAccounts], consumes=[text/html])
  value contains '/listAccounts'
  name = 'Newton'