1

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

Nathan Russell
  • 3,428
  • 5
  • 30
  • 51
  • can you give details about why it is not working ? and did you add `@Aspect` to your class ? – muhammed ozbilici Nov 18 '22 at 10:27
  • @muhammedozbilici - thanks. Yes, the class has `@Aspect` and `@Component`. In fact, both pointcut methods are in the same class - one works and the other doesnt. In respect of what "not working" means , it simply doesn't get triggered at all ¯\_(ツ)_/¯ – Nathan Russell Nov 18 '22 at 10:33

2 Answers2

1

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
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Kriegaex - thanks for your suggested answer but unfortunately it still doesn't work. The pointcut method never gets invoked. In addition, using JDK interface proxies causes other problems with other pointcuts whose classes are injected into other beans. EG: `Bean named 'queueMessageHandler' is expected to be of type 'io.awspring.cloud.messaging.listener.QueueMessageHandler' but was actually of type 'jdk.proxy3.$Proxy263'` – Nathan Russell Nov 21 '22 at 19:27
  • It's also worth noting that the aspect class has 2 pointcuts in it - one works and the other doesn't. Which makes me think its not an issue of proxies. Though I take your point that I should probably be targeting the interface method rather than the `final` method in the concrete class. Still doesn't work though, even when targeting the interface method – Nathan Russell Nov 21 '22 at 19:31
  • The error message above occurs because now you are proxying interfaces, but programming against class types, which is bad design. It is easy to fix, but you should always share an [MCVE](https://stackoverflow.com/help/mcve) for complex questions like this one, not just an incoherent set of snippets which nobody can compile and debug. If you would have posted a link to a minimal reproducer on GitHub, I would have been able to easily write a spot-on answer. Like this, I wrote a comprehensive answer, you then accepted your own, unrelated one and I finally wasted my time, trying to help you. – kriegaex Nov 21 '22 at 22:06
  • "but programming against class types, which is bad design". Please don't teach people to suck eggs, or otherwise assume they are in the wrong. The bean method that now fails because of not using an interface as it's injection parameter is in fact a Spring class: `io.awspring.cloud.autoconfigure.messaging.SqsAutoConfiguration`, `public SimpleMessageListenerContainer simpleMessageListenerContainer(AmazonSQSAsync amazonSqs, QueueMessageHandler queueMessageHandler)` – Nathan Russell Nov 22 '22 at 07:20
  • Isn't that what I said, that a JDK interface proxy is created, but the code expects a class type? Would you mind linking to that method? I can find class [`SqsAutoConfiguration`](https://docs.awspring.io/spring-cloud-aws/docs/current/apidocs/io/awspring/cloud/autoconfigure/messaging/SqsAutoConfiguration.html), but where is the method `simpleMessageListenerContainer`? – kriegaex Nov 22 '22 at 10:22
0

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.

Nathan Russell
  • 3,428
  • 5
  • 30
  • 51
  • Sorry, that does not solve your original problem, my answer does. This might be the solution to a follow-up problem, so please do not mix up everything. The problem you are mentioning here is a duplicate of a question which has been asked here at least 100 times and which I have answered comprehensively [here](https://stackoverflow.com/a/56616248/1082681), – kriegaex Nov 21 '22 at 22:00
  • I don't know how you feel this doesn't solve my original problem, when in fact it does. I answered my own question because its the right solution for my use case and providing an explained answer to my question helps the community, which is what SO is all about. – Nathan Russell Nov 22 '22 at 07:25
  • Like I said, there is no way that a CGLIB proxy can intercept calls to a final method. Therefore, some part of the information given is probably incorrect. – kriegaex Nov 22 '22 at 10:28