Search code examples
javaspringspring-bootannotationsaspect

Change method signature with @Aspect


Can you change a method's signature in Spring using aspects?

Like effectively transform the following:

@GetMapping("/thing")
@User // custom annotation that should authenticate the user
public ResponseEntity getThing() {
    ... // user is successfully authenticated, get the "thing" from the database
}

into:

@GetMapping("/thing")
public ResponseEntity getThing(@CookieValue("Session-Token") String sessionToken) {
    User user = authenticator.authenticateSessionTokenOrThrow(sessionToken);
    ... // user is successfully authenticated, get the "thing" from the database
}

With the user variable also becoming available for use in the method body.

If not, how can I achieve the same result without repeating the code (parameter and authenticator call) everywhere?


Solution

  • Aspects aren't meant for that.

    Yes, they can effectively modify .class files bytecode, with compile time or run time weaving, but they do not override methods' signatures.

    Also, the default Spring AOP Aspects are implemented in pure Java, and thus cannot touch the bytecode layer. For that you'd need AspectJ.

    Tools for customizing bytecode at run/compile time are ASM, ByteBuddy, CGLIB or Javassist.


    However, you can probably accomplish this via an Annotation Processor, which lets you modify the actual sources, instead of the already compiled bytecode.


    If not, how can I achieve the same result without repeating the code (parameter and authenticator call) everywhere?

    Possible solutions are

    1. HandlerInterceptor, which simply throws an Exception if the user isn't authenticated
    2. Standard Spring AOP advice, which simply throws an Exception if the user isn't authenticated
    3. Spring Security

    1 is pretty easy.
    2 is more time-consuming
    3 imho, seems the best match for authentication, but it's the most complex, probably


    The HandlerInterceptor can choose which methods it applies to?

    No, unfortunately. I had a requirement a couple of months ago to "cover" only certain methods with an Interceptor, and I implemented a custom solution, which simply look for an annotation specified on the method itself.

    This is an extract of my custom HandlerInterceptor, which looks for the CheckInit annotation, first on the type, and then on the method, for a more specific customization.

    @Override
    public boolean preHandle(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final Object handler
    ) throws Exception {
        if (handler instanceof HandlerMethod) {
            if (shouldCheckInit((HandlerMethod) handler)) {
                checkInit();
            }
        }
    
        return true;
    }
    
    private static boolean shouldCheckInit(final HandlerMethod handlerMethod) {
        final var typeAnnotation = handlerMethod.getBeanType().getAnnotation(CheckInit.class);
        final var shouldCheckInit = typeAnnotation != null && typeAnnotation.value();
    
        final var methodAnnotation = handlerMethod.getMethodAnnotation(CheckInit.class);
        return (methodAnnotation == null || methodAnnotation.value()) && shouldCheckInit;
    }
    
    private void checkInit() throws Exception {
        if (!manager.isActive()) {
            throw new NotInitializedException();
        }
    }
    

    The "Standard Spring AOP advice" seems interesting, do you have a link for that?

    Spring AOP documentation - look for the Java-based configuration (I hate XML)

    AspectJ really touches the bytecode and can modify signatures as well?

    You could make AspectJ modify signatures. Just fork the project and modify its Java Agent or compiler.

    AFAIK Annotation Processors cannot modify classes, they can only create new ones.

    The thing is, they don't modify .class files, instead they modify source files, which means they simply edit them. E.g. Lombok uses annotation processing to modify source files.

    But yes, the modified sources are written to a new file.