4

I am stuck with a problem in spring boot. I am trying to give extra functionality to some RestControllers, and I am trying to achieve it with some custom annotations. Here is an example.

My annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomAnnotation {
  String someArg();
}

My aspect:

@Aspect
@Component
public class MyAspect {
  @Around(
    value = "@annotation(MyCustomAnnotation)",
    argNames = "proceedingJoinPoint,someArg"
  )
  public Object addMyLogic(ProceedingJoinPoint proceedingJoinPoint, String someArg)
    throws Throwable
  {
    System.out.println(someArg);
    return proceedingJoinPoint.proceed();
  }
}

My method:

@MyCustomAnnotation(someArg = "something")
@GetMapping("/whatever/route")
public SomeCustomResponse endpointAction(@RequestParam Long someId) {
  SomeCustomResult result = someActionDoesNotMatter(someId);
  return new SomeCustomResponse(result);
}

Mostly based on the docs (https://docs.spring.io/spring/docs/3.0.3.RELEASE/spring-framework-reference/html/aop.html - 7.2.4.6 Advice parameters) I am pretty sure, it should work.

I am here, because it does not...

What drives me crazy, is that even Intellij, when tries to help with argNames (empty string -> red underline -> alt+enter -> Correct argNames attribute) gives me this, and keeps it red...

Based on the docs, proceedingJoinPoint is not even needed (it does not work without it either): "If the first parameter is of the JoinPoint, ProceedingJoinPoint..."

With the current setup, it says "Unbound pointcut parameter 'someArg'"

At this point, I should also note, that without the args it is working fine.

I have two questions, actually:

  1. Why does this does not work? (That was pretty obvious)

  2. If I would like to give some extra functionality to some controllers, and I would like to parameterise it from the outside, is it the right pattern in spring boot? (With python, it was quite easy to do this with decorators - I am not quite sure, that I am not misguided by the similar syntax)

One example (the example above was pretty abtract):

I would like to create a @LogEndpointCall annotation, and the developer of a route can later just put it on the endpoint that he is developing

...however, it would be nice, if he could add a string (or more likely, an enum) as a parameter

@LogEndpointCall(EndpointCallLogEnum.NotVeryImportantCallWhoCares)

or

@LogEndpointCall(EndpointCallLogEnum.PrettySensitiveCallCheckItALot)

so that the same logic is triggered, but with a different param -> and a save to a different destination will be made.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Balint Toth
  • 41
  • 1
  • 3

2 Answers2

8

You cannot directly bind an annotation property to an advice parameter. Just bind the annotation itself and access its parameter normally:

@Around("@annotation(myCustomAnnotation)")
public Object addMyLogic(
  ProceedingJoinPoint thisJoinPoint,
  MyCustomAnnotation myCustomAnnotation
)
  throws Throwable
{
  System.out.println(thisJoinPoint + " -> " + myCustomAnnotation.someArg());
  return thisJoinPoint.proceed();
}

It will print the something like this with Spring AOP

execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something

and something like this with AspectJ (because AJ also knows call joinpoints, not just execution)

call(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • 2
    I had problems when using the full package name. I was trying this: @Around("@annotation(com.xyz.myCustomAnnotation)"). When I needed to just put the name of the annotation as shown in the answer above: @Around("@annotation(myCustomAnnotation)"). The above answer helped me access the annotation and it's parameters. – JordRoss Jul 21 '20 at 20:59
  • 1
    You _have to_ use the full package name if you do _not_ bind the annotation to a method parameter and you _must not_ use it if you do. This is logical because if there is a parameter, the type can be determined easily. If there is none, the type needs to be specified exactly because it cannot be inferred from an imported class. – kriegaex Jul 22 '20 at 00:45
1

If you want your method to intercept method that take on consideration args you must explicit mention that in you pointcut expression , to make it work here is what you should do :

@Around(
value = "@annotation(MyCustomAnnotation) && args(someArg)",
argNames = "someArg")

notice that i add && args(someArg), you can add as much arguments as you want, in argNames you can omit proceedingJoinPoint.

elmehdi
  • 449
  • 2
  • 10
  • 1
    This answer is wrong. The question is not about binding method parameters but about accessing parameters of a bound annotation. – kriegaex Feb 14 '18 at 13:15