Spring AOP pointcut execution not working

375 Views Asked by At

I'm working on a Spring Boot project that uses Spring Cloud (io.awspring.cloud:spring-cloud-aws-dependencies:2.4.2) to produce and consume AWS SQS messages. I have several message producers and several message consumers, and all is working fine from that perspective.

I now have a cross cutting concern where I need to set a header on all messages being produced/sent; and to read that header on all messages being consumed (correlationId), and AOP seems like a good fit.

My aspect for handling (receiving) a message works fine:

    @Before("execution(* org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(..))")
    fun beforeHandleMessage(joinPoint: JoinPoint) {

The class and method that it is targeting is:

package org.springframework.messaging.handler.invocation;
...
public abstract class AbstractMethodMessageHandler<T>
        implements MessageHandler, ApplicationContextAware, InitializingBean {
...
    @Override
    public void handleMessage(Message<?> message) throws MessagingException {

As mentioned, this works great.

However, I can't get my pointcut for sending a message working. This is my aspect:

    @Before("execution(* org.springframework.messaging.support.AbstractMessageChannel.send(..))")
    // @Before("execution(* io.awspring.cloud.messaging.core.QueueMessageChannel.send(..))")
    fun beforeSendMessage(joinPoint: JoinPoint) {

And the class and method that I'm trying to target is this:

package org.springframework.messaging.support;
...
public abstract class AbstractMessageChannel implements MessageChannel, InterceptableChannel, BeanNameAware {
...
    @Override
    public final boolean send(Message<?> message) {

But it doesn't seem to work. I've also tried writing the pointcut to target the concrete implementation class (as commented out above), but that also does nothing.

I can't see what the difference is between my working pointcut for beforeHandleMessage and beforeSendMethod, other than the pointcut for beforeSendMethod is targeting a final method. Is that relevant?

Any pointers to get this working would be very much appreciated;
Thanks

2

There are 2 best solutions below

3
Nathan Russell On BEST ANSWER

I found the answer from this other SO answer: Spring AOP ignores some methods of Hessian Service

I know that Spring AOP won't intercept local method calls. I.e. the proxy which is applied doesn't intercept the calls if the same object calls its own method, even if it matches the pointcut expression.

The problem was that the send method I was targeting was called by a number of other methods in the class.

Looking at the call stack I found a different method that was the first method called in the class. Changing the pointcut to target that method has worked.

5
kriegaex On

Spring AOP uses dynamic proxies, i.e. it works by subclassing (CGLIB proxy) or by implementing interfaces (JDK proxies). In your case, you are targeting a class method rather than an interface method. The class method is final, which explains why it cannot work that way, because you cannot override a final method in a CGLIB proxy. What you should do instead is to

  • target the interface method MessageChannel.send(Message) and
  • make sure to use JDK proxies, i.e. not the "proxy target class" (CGLIB) mode. In Spring core, JDK proxy mode is the default, in Spring Boot CGLIB mode. So in Boot, you need to manually reconfigure the framework to permit for JDK proxies, which is only possible there via config file, not via annotations (they come too late in the bootstrapping process for Boot).

More specifically, you need this in src/main/resources/application.properties for Spring Boot:

# This works, now we can create JDK interface proxies. The seemingly equivalent alternative
#   @EnableAspectJAutoProxy(proxyTargetClass = false)
# where 'false' is even the default, does *not* work in Spring Boot.
spring.aop.proxy-target-class=false