简体   繁体   中英

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

Is there a way to directly get hold of a method(non-static) annotated with given annotation present in a given object? I don't want to iterate over list of all methods and check if the given annotation present or not. In below sample code, I have used dummy method(not exist) getMethodAnnotatedWith() . I need to replace this with actual method.

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) can be used to achieve the requirement.

API used in the example code : MethodUtils.getMethodsListWithAnnotation

Aspect Code

@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[] {});
        }
    }
}

Note : The Retention Policy of MyValidator annotation is RUNTIME for this code to work.

If the InputRequest instance obtained in the aspect is a proxy , the code will need to be modified to get the actual class of the same.

Here is my stand-alone AspectJ MCVE . I just imported some Spring classes. The syntax would be the same in Spring AOP.

Helper classes:

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 {}

Custom validator interface ( not annotation):

package de.scrum_master.app;

public interface MyValidator {
  boolean validate();
}

Class implementing custom validator interface:

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

See? Instead of annotating the validator method, you just override the interface method. It is just as simple as before, but more type-safe and feels more "standard-ish". It will also be much easier to handle by AOP, as you are going to find out below.

Controller:

The controller looks just the same as before. You still have the method annotated with @ValidateAction and taking InputRequest as its first parameter.

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

Sample driver application:

package de.scrum_master.app;

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

Aspect:

The aspect is super simple now, like I said in my comment to your question. The pointcut checks for methods which

  • are annotated with @ValidateAction and
  • have a first parameter implementing MyValidator .

Then it binds the MyValidator parameter to an advice method argument by args() .

Please note that you can omit the trailing && execution(* *(..)) in Spring AOP because it only supports method execution() joinpoints, while in AspectJ there are also call() joinpoints which here would lead to double validation and log output.

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

Console log:

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

Update answering follow-up questions: Please read some documentation. The Spring manual is a good source.

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

It is called argument binding. This specific pointcut designator means: Match all target methods where the first argument matches the type of validator in the advice method arguments list and bind the value to that argument. As you see, the argument is declared as MyValidator validator . The , .. means that any subsequent target method arguments (if any) do not matter. For more information see 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 meaning what? Maybe framework? Anyway, I do not see the problem. Why would the framework have to figure out which implementation there is? That is the beauty of OOP and virtual methods: You do not need to figure out anything because each implementation has a boolean validate() which then gets called. It is simple, type-safe, hassle-free. Your solution is neither of these. This approach just works. Instead of asking, why don't you just try?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM