Issue
I am learning Spring AOP and I know that in @Around advice we use Object return type because the return value of the target method can be of any type. But my question is when the return value is downcasted to actual return type? Does Proxy downcasts it before sending it to main method(where the target method was called)?
In Main -
String result = account.getAccountHolderName();
In aspect class -
@Around("execution(* getAccountHolderName())")
public Object myAroundAdvice(ProceedingJoinPoint joinPoint)
{
Object result = joinPoint.proceed();
return result;
}
I want to know when the result is downcasted to String class after returning from the advice as an Object
Solution
There is no casting within the Spring AOP core classes themselves, because they only pass along Object
s when delegating method calls to AOP proxies. The user is responsible to return the correct type from the @Around
advice. But of course, there is a final cast, which you can clearly see if you e.g. make the advice return something other than the actual return type of the intercepted method. Then you will see an exception like:
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class de.scrum_master.spring.q59783423.HoldResponse (java.lang.String is in module java.base of loader 'bootstrap'; de.scrum_master.spring.q59783423.HoldResponse is in unnamed module of loader 'app')
at de.scrum_master.spring.q59783423.HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.execute(<generated>)
at de.scrum_master.spring.q59783423.MyApplication.doStuff(MyApplication.java:46)
at de.scrum_master.spring.q59783423.MyApplication.main(MyApplication.java:22)
In this example, I made my around advice falsely return a String
instead of a HoldResponse
. You can see that the ClassCastException
occurs within the dynamic proxy generated by Spring, in this case a CGLIB proxy (could also be a JDK proxy, depending on the circumstances). So there is actually a cast happening, otherwise there would not be a class cast exception.
When dumping the proxy's byte code using the little agent I described in this answer, you will see something like:
public final doExecute(Lde/scrum_master/spring/q59783423/HoldRequest;)Lde/scrum_master/spring/q59783423/HoldResponse; throws de/scrum_master/spring/q59783423/PaymentServiceException
ALOAD 0
GETFIELD de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.CGLIB$CALLBACK_0 : Lorg/springframework/cglib/proxy/MethodInterceptor;
DUP
IFNONNULL L0
POP
ALOAD 0
INVOKESTATIC de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.CGLIB$BIND_CALLBACKS (Ljava/lang/Object;)V
ALOAD 0
GETFIELD de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.CGLIB$CALLBACK_0 : Lorg/springframework/cglib/proxy/MethodInterceptor;
L0
DUP
IFNULL L1
ALOAD 0
GETSTATIC de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.CGLIB$doExecute$0$Method : Ljava/lang/reflect/Method;
ICONST_1
ANEWARRAY java/lang/Object
DUP
ICONST_0
ALOAD 1
AASTORE
GETSTATIC de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService$$EnhancerBySpringCGLIB$$13706881.CGLIB$doExecute$0$Proxy : Lorg/springframework/cglib/proxy/MethodProxy;
INVOKEINTERFACE org/springframework/cglib/proxy/MethodInterceptor.intercept (Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;Lorg/springframework/cglib/proxy/MethodProxy;)Ljava/lang/Object; (itf)
CHECKCAST de/scrum_master/spring/q59783423/HoldResponse
ARETURN
L1
ALOAD 0
ALOAD 1
INVOKESPECIAL de/scrum_master/spring/q59783423/HoldPaymentOrchestrationService.doExecute (Lde/scrum_master/spring/q59783423/HoldRequest;)Lde/scrum_master/spring/q59783423/HoldResponse;
ARETURN
MAXSTACK = 7
MAXLOCALS = 2
Please especially note
INVOKEINTERFACE org/springframework/cglib/proxy/MethodInterceptor.intercept (Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;Lorg/springframework/cglib/proxy/MethodProxy;)Ljava/lang/Object; (itf)
CHECKCAST de/scrum_master/spring/q59783423/HoldResponse
I.e., the proxy calls org.springframework.cglib.proxy.MethodInterceptor#intercept
and then casts the result to the intercepted method's return type. There you have your Spring AOP magic.
Interesting question, actually. 👍
BTW, if you wish to make your around advice more specific, you may give it a return type other than Object
. But then you need to cast the result of proceed()
before returning it. In Spring AOP, you are responsible for declaring a return type matching the intercepted method(s). In native AspectJ, declaring a specific return type would automatically narrow down matching to joinpoints compatible with the return type, which is not the case in Spring AOP.
Answered By - kriegaex
Answer Checked By - Mildred Charles (JavaFixing Admin)