(Title isn't the best, but I couldn't find a good way to phrase the following problem)
Given
@Aspect
@Component
class MyAspect {
@Autowired private MyService service;
@Around("@target(org.springframework.ws.server.endpoint.annotation.Endpoint)")
public Object aroundEndpoint(ProceedingJoinPoint joinPoint) {
return service.around(joinPoint::proceed);
}
@Around("@target(org.springframework.stereotype.Service)") // And some other expressions to exclude `MyService`
public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable {
// ...
}
}
@Service
class MyService {
// My own Callable<T> with Throwable instead of Exception
public Object around(Callable<?> callable) throws Throwable {
// Do stuff
Object returnValue = callable.call();
// Do stuff
return returnValue;
}
}
When an endpoint method is invoked, it is intercepted by aroundEndpoint
. If I were to invoke joinPoint.proceed()
right away, everything would work as expected. However, if I pass it as a method reference (or a lambda) into MyService.around
, and then invoke it, it is matched against my service pointcut and my around service advice is applied to it.
I did some debugging, and here's what I see: in AspectJExpressionPointcut.matches
, thisObject
and targetObject
refer to my endpoint in the former case, but refer to my service in the latter case. This is probably because it uses ExposeInvocationInterceptor.currentInvocation()
, and doing another method call messes with it.
Is this a bug? Some limitation of the proxy-based approach? Or do I have to simply inline MyService.aroundService
?
I reproduced your problem and also compared a similar setup in plain Java + AspectJ (i.e. without Spring or Spring AOP, only using two Spring annotations used in aspect pointcuts). There the problem does not occur. It is something specific to Spring AOP, that much is sure.
Now Spring uses AspectJ's pointcut matching in combination with its own AOP framework based on proxies and delegation. Somewhere in there this edge case must mess up the status of Spring aspect matching, causing the behaviour you see. I have not debugged into it so far, but from what I see now I would suggest to create an issue and see what the maintainers say about it.
Here is my AspectJ MCVE proving that the problem does not occur there. BTW, I had to rename the package aspect
to aop
because in AspectJ aspect
is a reserved keyword. But I also renamed it in the Spring project in order to make sure it is unrelated to the problem at hand, and it is unrelated.
package aop.mcve;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
public void controllerMethod() {}
}
package aop.mcve;
import org.springframework.stereotype.Service;
@Service
public class MyService {
public Object delegateTo(MyAspect.Callable<?> callable) throws Throwable {
return callable.call();
}
public void serviceMethod() {}
}
package aop.mcve;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAspect {
private final MyService myService = new MyService();
@Pointcut("within(aop.mcve..*) && !within(MyAspect) && execution(* *(..))")
public void inDomain() {}
@Pointcut("@target(org.springframework.stereotype.Service)")
public void inService() {}
@Pointcut("execution(* aop.mcve.MyService.*(..))")
public void inMyService() {}
@Pointcut("@target(org.springframework.web.bind.annotation.RestController)")
public void inController() {}
@Around("inDomain() && inController()")
public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aroundController -> " + joinPoint);
return myService.delegateTo(joinPoint::proceed);
}
@Around("inDomain() && inService() && !inMyService()")
public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aroundService -> " + joinPoint);
System.out.println("You should never see this message!");
return joinPoint.proceed();
}
public interface Callable<T> {
T call() throws Throwable;
}
}
package aop.mcve;
public class AspectMcveApplication {
public static void main(String[] args) throws Throwable {
new MyService().serviceMethod();
new MyController().controllerMethod();
}
}
The console log:
aroundController -> execution(void aop.mcve.MyController.controllerMethod())
As you can see, the advice method aroundService(..)
does not get triggered like in Spring AOP.
Update: I modified your MCVE in order to make it runnable with both Spring AOP and AspectJ, it automatically detects AspectJ's load-time weaver when active. I sent you this pull request.