如何在 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 手册是一个很好的来源。
- what does it mean
&& args(validator, ..)
?
这叫做参数绑定。这个特定的切入点指示符意味着:匹配第一个参数与建议方法参数列表中的 validator
类型匹配的所有目标方法,并将值绑定到该参数。如您所见,参数声明为 MyValidator validator
。 , ..
意味着任何后续的目标方法参数(如果有的话)都无关紧要。有关详细信息,请参阅 this manual paragraph。
- 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。您的解决方案不是这些。这种方法很管用。与其问,不如试试?
有没有办法直接获取在给定对象中使用给定注释进行注释的方法(非静态)?我不想遍历所有方法的列表并检查给定的注释是否存在。 在下面的示例代码中,我使用了虚拟方法(不存在) 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 手册是一个很好的来源。
- what does it mean
&& args(validator, ..)
?
这叫做参数绑定。这个特定的切入点指示符意味着:匹配第一个参数与建议方法参数列表中的 validator
类型匹配的所有目标方法,并将值绑定到该参数。如您所见,参数声明为 MyValidator validator
。 , ..
意味着任何后续的目标方法参数(如果有的话)都无关紧要。有关详细信息,请参阅 this manual paragraph。
- 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。您的解决方案不是这些。这种方法很管用。与其问,不如试试?