Peter Kronenberg
Peter Kronenberg

Reputation: 1296

Aspect capturing additional Pointcuts

I've been getting these messages at startup of my Spring Boot service.

Unable to proxy interface-implementing method [public final void org.springframework.web.filter.OncePerRequestFilter.doFilter(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.FilterChain) throws javax.servlet.ServletException,java.io.IOException] because it is marked as final: Consider using interface-based JDK proxies instead!

Unable to proxy interface-implementing method [public final void org.springframework.web.filter.GenericFilterBean.init(javax.servlet.FilterConfig) throws javax.servlet.ServletException] because it is marked as final: Consider using interface-based JDK proxies instead!

The problem's I can't figure out what classes are getting selected by my Pointcut that is causing this:

My Aspect file:

@Around("execution(public * com.torchai..*.*(..)) "
        + "&& loggingEnabledAndNotDisabled()"
        + "&& !allPublicControllerMethodsNotDisabled()"
        + "&& !allPublicControllerLayerMethodsNotDisabled()"
        + "&& !allPublicServiceMethodsNotDisabled()"
        + "&& !allPublicServiceLayerMethodsNotDisabled()")
public Object allPublicMethodsNotDisabledAndNotControllerOrService(
              final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    return log(proceedingJoinPoint, LogEvent.LAYER_OTHER);
}

@AfterThrowing(
        pointcut = "execution(public * com.torchai..*.*(..)) "
                + "&& loggingEnabledAndNotDisabled()"
                + "&& !allPublicControllerMethodsNotDisabled()"
                + "&& !allPublicControllerLayerMethodsNotDisabled()"
                + "&& !allPublicServiceMethodsNotDisabled()"
                + "&& !allPublicServiceLayerMethodsNotDisabled()",
        throwing = "t")
public void allPublicMethodsNotDisabledAndNotControllerOrService(
            final JoinPoint joinPoint, final Throwable t) {
    onException(joinPoint, t, LogEvent.LAYER_OTHER);
}

I'm not pasting in all the supporting Pointcuts, but you can see they are the same for @Around and @AfterThrowing. These points cuts should not be selecting anything at this point.

What I find really interesting is if I totally comment out the @Around code, the problem is still there.

But if I leave the @Around code and comment out @AfterThrowing, the problem is gone.

So even though these 2 things should be equivalent in terms of what they select, for some reason @AfterThrowing is selecting something that is causing the problem.

Update

@kriegaex Thanks for your through response. Here are my other pointcuts. Was just trying to keep things simple by not including them.


    @Pointcut("@annotation(com.torchai.service.aspect.annotations.AspectLog) "
            + "|| @target(com.torchai.service.aspect.annotations.AspectLog)")
    public void methodOrClassLoggingEnabled() {
    }

    @Pointcut("!@annotation(com.torchai.service.aspect.annotations.AspectNoLog)")
    public void methodLoggingNotDisabled() {
    }

    @Pointcut("methodOrClassLoggingEnabled() && methodLoggingNotDisabled()")
    public void loggingEnabledAndNotDisabled() {
    }

    /**
     * Any public methods in classes which are specifically annotated as a Controller
     */
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) ||"
            + "within(@org.springframework.stereotype.Controller *)")
    public void allPublicControllerMethods() {
    }

    /**
     * Any public methods in classes which are specifically annotated as a Service
     */
    @Pointcut("within(@org.springframework.stereotype.Service *)")
    public void allPublicServiceMethods() {
    }

    // Controller classes are enabled by default, so don't need to be specifically enabled.
    @Pointcut("allPublicControllerMethods() "
            + "&& methodLoggingNotDisabled()")
    public void allPublicControllerMethodsNotDisabled() {
    }

    // Service classes are enabled by default, so don't need to be specifically enabled.
    @Pointcut("allPublicServiceMethods() "
            + "&& methodLoggingNotDisabled()")
    public void allPublicServiceMethodsNotDisabled() {
    }

    // Components which aren't Service classes, but in the service package need to be enabled with @AspectLog
    @Pointcut("execution(public * com.torchai.service..service..*(..)) "
            + "&& loggingEnabledAndNotDisabled()")
    public void allPublicServiceLayerMethodsNotDisabled() {
    }

    // Components which aren't Controller classes, but in the controller package need to be enabled with @AspectLog
    @Pointcut("execution(public * com.torchai.service..controller..*(..)) "
            + "&& loggingEnabledAndNotDisabled()")
    public void allPublicControllerLayerMethodsNotDisabled() {
    }

    @Around("allPublicControllerMethodsNotDisabled() "
            + "|| allPublicControllerLayerMethodsNotDisabled()")
    public Object logController(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return log(proceedingJoinPoint, LogEvent.LAYER_CONTROLLER);
    }

    @Around("allPublicServiceMethodsNotDisabled() "
            + "|| allPublicServiceLayerMethodsNotDisabled()")
    public Object logService(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return log(proceedingJoinPoint, LogEvent.LAYER_SERVICE);
    }

I understand the basic meaning of the message, although admittedly, I'm not sure how to do the JDK proxy instead. But I don't think it really necessary. We do have a Filter class which implements OncePerRequestFilter, but I can't be sure that that is the one getting picked up, because it doesn't say.

But my real confusion is not just that a class is getting picked up that I didn't expect, but that it seems to only get picked up by the @AfterThrowing and not by @Around . Why wouldn't these be exactly the same?

Update #2 (a few minutes later)

I'm thinking this might have something to do with my used of execution as opposed to your suggestion of within. I don't fully understand the difference. But still, the definitions in @AfterThrowing and @Around are the same. And even where I'm using execution, it still needs to match the other conditions.

Update #3

I've created a repo that shows the problem: https://github.com/peterkronenberg/aoptest

At startup, Springboot issues these messages:

2022-03-24 15:58:57.261  INFO 35104 --- [           main] o.s.aop.framework.CglibAopProxy          : Unable to proxy interface-implementing method [public final void org.springframework.web.filter.OncePerRequestFilter.doFilter(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.FilterChain) throws javax.servlet.ServletException,java.io.IOException] because it is marked as final: Consider using interface-based JDK proxies instead!
2022-03-24 15:58:57.261  INFO 35104 --- [           main] o.s.aop.framework.CglibAopProxy          : Unable to proxy interface-implementing method [public final void org.springframework.web.filter.GenericFilterBean.init(javax.servlet.FilterConfig) throws javax.servlet.ServletException] because it is marked as final: Consider using interface-based JDK proxies instead!
2022-03-24 15:58:57.310 ERROR 35104 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Exception starting filter [authFilter]

The service doesn't start up, which is not what I was seeing originally. But maybe that provides more clues. But if I disabled the Aspect code, simply by commenting out the @Aspect annotation in the AspectLayer class, then it seems to work. Somehow, there is a conflict between my Aspect code and my Filter code

I do not want my AuthFilter class to be picked up by my Aspect code. I believe that's what is causing the problem, although I'm not sure why

Upvotes: 2

Views: 663

Answers (1)

kriegaex
kriegaex

Reputation: 67317

Let us inspect the error message with some extra line breaks:

Unable to proxy interface-implementing method
  public final void org.springframework.web.filter.OncePerRequestFilter.doFilter(
    javax.servlet.ServletRequest,
    javax.servlet.ServletResponse,
    javax.servlet.FilterChain
  )
  throws javax.servlet.ServletException,java.io.IOException

because it is marked as final:
  Consider using interface-based JDK proxies instead!

What that means is that

  • somewhere in your application (or in a dependency) you have a Filter class extending OncePerRequestFilter and
  • somehow Spring AOP is trying to proxy it.
  • The way it is trying to proxy it is by using CGLIB proxies.
  • CGLIB proxies work by creating subclasses and overriding methods so as to be able to intercept them and delegate to the original ones or skip them, whatever the developer prefers.
  • Spring framework class OncePerRequestFilter implements an interface method doFilter from Filter, but for some reason declares it final. I.e., when proxying the filter, the final method from the parent class cannot be proxied. If later you call it on the proxy instance, it actually ends up in the original object. You cannot hook into it with aspects, advisors or method interceptors.
  • The hint "consider using interface-based JDK proxies instead" means that if you need to hook into such a method, you could configure Spring to create a JDK proxy instead of a CGLIB proxy. That way, the proxy would directly implement methods of all interfaces the target class implements, and you could hook into those two final methods. But in that case, other target class methods which do not implement any interface methods cannot be intercepted anymore. It is a trade-off, choose whichever variant is better for you, because you cannot have both.

Now with regard to your astonishment that some classes are being targeted at all by Spring AOP, you need to understand that many pointcuts are being evaluated dynamically, especially if they contain things like this(), target(), @(target) etc. which need runtime information. Users are often surprised that their generic pointcuts seem to "proxy the world", often even including Spring's own framework beans. Usually, you can limit that by scoping the pointcuts with something like && within(org.acme.myapp..**). You can try that. Not being able to see your many pointcuts, I can only speculate what they might contain, but like so often in software development the devil is in the details.


Update after question edit: Like I speculated before, you are using dynamic pointcuts such as @target and @annotation and a bunch of within(@org.springframework..* *) stuff which also targets Spring's own beans. But what I am missing is some application-specific package scoping && within(org.acme.myapp..**), like I suggested before. So all of what I said before still holds. Neither have you reported back on whether you tried that and what the effect was, nor have you said if you found the bean(s) extending OncePerRequestFilter and GenericFilterBean.

Upvotes: 1

Related Questions