如何在 springboot / java 应用程序中获取使用给定注释注释的方法

How to get Method annotated with given annotation in springboot / java app

有没有办法直接获取在给定对象中使用给定注释进行注释的方法(非静态)?我不想遍历所有方法的列表并检查给定的注释是否存在。 在下面的示例代码中,我使用了虚拟方法(不存在) getMethodAnnotatedWith() 。我需要用实际方法替换它。

public class MyController {
  @PostMapping("/sum/{platform}")
  @ValidateAction
  public Result sum(@RequestBody InputRequest input, @PathVariable("platform") String platform) {
    log.info("input: {}, platform: {}", input, platform);
    return new Result(input.getA() + input.getB());
  }
}
class InputRequest {
  @NotNull
  private Integer a;
  @NotNull
  private Integer b;

  @MyValidator
  public boolean customMyValidator() {
    log.info("From customMyValidator-----------");
    return false;
  }
}
@Aspect
@Component
@Slf4j
public class AspectClass {
  @Before(" @annotation(com.example.ValidateAction)")
  public void validateAspect(JoinPoint joinPoint) throws Throwable {
    log.info(" MethodName : " + joinPoint.getSignature().getName());
    Object[] args = joinPoint.getArgs();
    log.info("args[0]==>"+args[0] +", args[1]==>"+args[1]);

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    Parameter[] parameters = method.getParameters();
    Method customMyValidator = parameters[0].getType().getMethodAnnotatedWith(MyValidator.class);  // InputRequest class type which has a method annotated with @MyValidator

    customMyValidator.invoke(args[0]);
  }
}

MethodUtils (Apache Commons Lang) 可以用来实现需求。

API 在示例代码中使用:MethodUtils.getMethodsListWithAnnotation

看点代码

@Component
@Aspect
public class CallAnnotatedMethodAspect {

    @Pointcut("within(rg.so.q64604586.service.*) && @annotation(rg.so.q64604586.annotation.ValidateAction)")
    public void validateActionMethod() {
    };

    @Before("validateActionMethod() && args(request,..)")
    public void adviceMethod(InputRequest request)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        List<Method> methodList = MethodUtils.getMethodsListWithAnnotation(request.getClass(), MyValidator.class);
        for (Method m : methodList) {
            m.invoke(request, new Object[] {});
        }
    }
}

注意:MyValidator 注释的保留策略是此代码运行的运行时。

如果在切面中获取的InputRequest实例是代理,则需要修改代码以获取相同的实际class。

这是我的 stand-alone AspectJ MCVE。我刚刚导入了一些 Spring 类。 Spring AOP 中的语法相同。

帮手类:

package de.scrum_master.app;

public class Result {
  public Result(int i) {}
}
package de.scrum_master.app;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target(METHOD)
public @interface ValidateAction {}

自定义验证器接口(不是注解):

package de.scrum_master.app;

public interface MyValidator {
  boolean validate();
}

Class 实现自定义验证器接口:

package de.scrum_master.app;

public class InputRequest implements MyValidator {
  private Integer a;
  private Integer b;

  public InputRequest(Integer a, Integer b) {
    this.a = a;
    this.b = b;
  }

  @Override
  public boolean validate() {
    System.out.println("Performing custom validation");
    return false;
  }

  public Integer getA() {
    return a;
  }

  public Integer getB() {
    return b;
  }

  @Override
  public String toString() {
    return "InputRequest(a=" + a + ", b=" + b + ")";
  }
}

看到了吗?您无需注释验证器方法,只需覆盖接口方法即可。和以前一样简单,只是多了type-safe,感觉多了“standard-ish”。它也将更容易通过 AOP 处理,正如您将在下面找到的那样。

控制器:

控制器看起来和以前一样。您仍然使用 @ValidateAction 注释的方法并将 InputRequest 作为其第一个参数。

package de.scrum_master.app;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

public class MyController {
  @PostMapping("/sum/{platform}")
  @ValidateAction
  public Result sum(@RequestBody InputRequest input, @PathVariable("platform") String platform) {
    System.out.println("input: " + input + ", platform: " + platform);
    return new Result(input.getA() + input.getB());
  }
}

示例驱动程序应用程序:

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    new MyController().sum(new InputRequest(11, 22), "foo");
  }
}

看点:

这方面现在非常简单,就像我在对你的问题的评论中所说的那样。切入点检查方法 which

  • 被注释为 @ValidateAction
  • 第一个参数实现 MyValidator

然后它通过 args().

MyValidator 参数绑定到建议方法参数

请注意,您可以在 Spring AOP 中省略尾随 && execution(* *(..)),因为它仅支持方法 execution() 连接点,而在 AspectJ 中也有 call() 连接点这里会导致双重验证和日志输出。

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import de.scrum_master.app.MyValidator;

@Aspect
@Component
public class MyValidatorAspect {
  @Before("@annotation(de.scrum_master.app.ValidateAction) && execution(* *(..)) && args(validator, ..)")
  public void validateAspect(JoinPoint joinPoint, MyValidator validator) throws Throwable {
    System.out.println(joinPoint);
    validator.validate();
  }
}

控制台日志:

execution(Result de.scrum_master.app.MyController.sum(InputRequest, String))
Performing custom validation
input: InputRequest(a=11, b=22), platform: foo

更新回答 follow-up 问题: 请阅读一些文档。 Spring 手册是一个很好的来源。

  1. what does it mean && args(validator, ..)?

这叫做参数绑定。这个特定的切入点指示符意味着:匹配第一个参数与建议方法参数列表中的 validator 类型匹配的所有目标方法,并将值绑定到该参数。如您所见,参数声明为 MyValidator validator, .. 意味着任何后续的目标方法参数(如果有的话)都无关紧要。有关详细信息,请参阅 this manual paragraph

  1. What would happen if more than one class implementing MyValidator interface . I mean how would FW figure out that which implementation has to passed while invoking current controller operation ?

FW是什么意思?也许框架?无论如何,我没有看到问题。为什么框架必须弄清楚有哪个实现?这就是 OOP 和虚拟方法的美妙之处:您不需要弄清楚任何事情,因为每个实现都有一个 boolean validate() 然后被调用。很简单,type-safe,hassle-free。您的解决方案不是这些。这种方法很管用。与其问,不如试试?