如何在springboot / Java应用程序中使用给定注释注释方法

时间:2020-10-30 08:01:35

标签: java spring-aop java-reflection

有没有一种方法可以直接获取在给定对象中存在给定注释的方法(非静态)?我不想遍历所有方法的列表,并检查给定的注释是否存在。 在下面的示例代码中,我使用了虚拟方法(不存在) 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]);
  }
}

2 个答案:

答案 0 :(得分:1)

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实例是代理,则需要修改代码以获取相同的实际类。

答案 1 :(得分:1)

这是我的独立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();
}

实现自定义验证程序接口的类:

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 + ")";
  }
}

看到了吗?无需注释验证器方法,您只需覆盖接口方法。它和以前一样简单,但是类型安全性更高,并且感觉更“标准”。通过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");
  }
}

方面:

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

  • @ValidateAction
  • 注释
  • 具有实现MyValidator的第一个参数。

然后将MyValidator的{​​{1}}参数绑定到通知方法参数。

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

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();
  }
}

更新回答的后续问题:请阅读一些文档。 Spring手册是很好的资料。

  1. execution(Result de.scrum_master.app.MyController.sum(InputRequest, String)) Performing custom validation input: InputRequest(a=11, b=22), platform: foo 是什么意思?

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

  1. 如果多个类实现, ..接口会发生什么。我的意思是,FW将如何确定调用当前控制器操作时必须通过哪个实现?

FW是什么意思?也许框架?无论如何,我看不出问题所在。为什么框架必须找出其中的实现?这就是OOP和虚拟方法的优点:您无需弄清楚什么,因为每个实现都有一个MyValidator,然后可以调用它。它简单,类型安全,无忧。您的解决方案都不是这些。这种方法是行得通的。不用问,为什么不尝试呢?

相关问题