I'm going to talk about 'self-calls' here, so if you have an experience with that feel free to skip the further material.
Please note that the spring reference describes the same problem - 8.6.1 Understanding AOP proxies. However, I found a lot of posts at the spring forums showing that people are unaware about it. So, I tried to explain it a bit wider/using another words.
Lets create a test-case that illustrates the problem. Suppose we're new to spring and just created a cool code that uses spring aop and works fine:
AopService.java
package com.spring.aop;
import org.springframework.stereotype.Component;
@Component
public class AopService {
public void service() {
System.out.println("AopService.service()");
}
public void anotherService() {
System.out.println("AopService.anotherService()");
}
}
TestAspect.java
package com.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TestAspect {
@Around("execution(* com.spring.aop.AopService.*(..))")
public Object advice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("TestAspect.advice()");
return joinPoint.proceed();
}
}
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.spring.aop"/>
<aop:aspectj-autoproxy/>
</beans>
SpringStart.java
package com.spring;
import com.spring.aop.AopService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringStart {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
AopService service = context.getBeansOfType(AopService.class).values().iterator().next();
service.service();
service.anotherService();
}
}
So, we see that there is a simple service class that just prints to stdout that its methods are called and simple aspect that intercepts all public method calls of that service and prints information about interception to the stdout as well. If we run SpringStart class we see the expected output - target service methods are intercepted by the aspect:
TestAspect.advice()
AopService.service()
TestAspect.advice()
AopService.anotherService()
Lets expand our service class a little:
AopService.java
package com.spring.aop;
import org.springframework.stereotype.Component;
@Component
public class AopService {
public void service() {
System.out.println("AopService.service()");
}
public void anotherService() {
System.out.println("AopService.anotherService(). Calling AopService.service()...");
service();
}
}
We expect that the aspect is executed three times - service() and anotherService() calls from SpringStart.main() and service() call from AopService.anotherService(). However, if we run the example we can see that the aspect is executed only two times (for the calls issued from SpringStart.main()), i.e. service() call from AopService.anotherService() is not intercepted:
TestAspect.advice()
AopService.service()
TestAspect.advice()
AopService.anotherService(). Calling AopService.service()...
AopService.service()
As usual, problem root is tightly connected to The Law of Leaky Abstractions. I.e. we had particular expectations about aspect behavior but didn't realize that spring aop principles doesn't allow the expectations to occur.
The general idea is that spring AOP is proxy-based, i.e. it assumes that when the bean is used as a dependency and its method(s) should be advised by particular aspect(s) the container injects aspect-aware bean proxy instead of the bean itself. For example, returned proxy uses approximately the same algorithm as the one below:
public class AopServiceProxy {
// Assuming that corresponding setters are introduced and the fields are defined.
private TestAspect aspect;
private AopService rawService;
public void service() {
aspect.advice();
rawService.service();
}
public void anotherService() {
aspect.advice();
anotherService();
}
}
I.e. the proxy is aspect-aware and it calls necessary aspect method before delegating the call to the 'raw' bean. However, last AopService implementation used to call service() from anotherService() - there is just no chance for the proxy to intercept that because the call is performed against 'this' reference, i.e. the call is performed on the 'rawBean' itself. That is the exact answer to why the aspect was not applied at that situation.
Now, when we understand the problem root we can talk about the ways to solve it. There are at least three ways:
- rewrite the code in order to avoid self-calls that should be advised by the aspects - rather inconvenient, especially if you work with the legacy code;
- replace self-calls with aspect-aware proxy calls, i.e. use the statement like ((AopService) AopContext.currentProxy()).service() instead of service() at the anotherService() - rather inconvenient too, requires additional proxy setup and totally couples the code to the spring;
- use aspectj weaving - that is my personal choice. Its idea is to inject aspect logic directly to the target class, i.e. remove the necessity to have all methods being proxied;
I'm going to create a small article that shows how to use various types of aspectj weaving with spring and that such approach completely eliminates the problem.

Great post!
ReplyDeleteAt first I thought that you were referring to the problem of service beans circularity together with proxies (such as for transaction management).
http://forum.springsource.org/showthread.php?p=252752
But then I saw that you were refering to something else, but it's still one of the problems that I also encountered in my work :)
Actually there is a 4th solution to your problem, if you are using dependency injection + programming against interfaces + using proxy-based AOP:
1) Define interfaces for each service in your application
2) Define implementing beans for each service interface
3a) If some service XXServiceImpl needs to call a method from another service, then inject that service as an interface (YYService), not as a class (YYServiceImpl)
3b) In this way, when Spring is injecting YYService into XXServiceImpl, it will first create an AOP proxy wrapped around the implementation (YYServiceImpl$Proxy66), and then it will inject the proxy into XXServiceImpl
3a') Better yet, to resolve the potential problem of circular references (that I mentioned at the start of my comment), define a "ServicesRepository" to hold references to the other services in your application module, and inject that ServicesRepository in your XXXXServiceImpl classes, instead of directly injecting the necessary services.
4) With this in place, instead of directly calling method1() from inside method2() like in your example (both being methods of the same service class+interface XXService/XXServiceImpl), instead you can call it like this:
getServicesRepository().getXXService().method1()
So every time you need to call a service method (no matter if it's from another service class, or from the same service class), then call it by getting a reference to the service from the "ServicesRepository".
This way you will always get back the proxy class, and not the target class.
And if you want to stop using AOP, your code will node have to change, because it will just call getServicesRepository().getXXXXService(), which will now just return the normal implementation class (injected by Spring).
Indeed, this moves away from the ideea of auto-wiring...
And indeed, I suppose that a better solution would be to not use proxy-based AOP in the first place, but use class-instrumentation-based AOP.
Thanks :)
ReplyDeleteThe whole idea of this post is to have a clear explanation of the most popular (from my point of view) spring aop problem that can be just referenced later from everywhere. I.e. if I see that collegue or a guy from forum/mailing list experiences that problem I just point him or her to this page instead of spending my time to explaining the problem one more time.
About your approach - it does resolves the problem but it doesn't look too natural. I mean that it seems a little bit illogical to see that the service uses tricky way to call its own method.
Ideal aspect use is when the target code knows nothing about it. If the code is changed in particular way to be 'applicable' by aspects that's wrong :(
Anyway, aspectj weaving resolves the problem at the very elegant way. Thanks to spring developers they added that little cool features that makes aspectj weaving even more convenient (load-time weaving configuration, 'aspectj' spring transactions mode etc). I'm going to expand that in details later.
Good approach is also:
ReplyDelete1. Declare the bean as abstract
2. Declare the method:
protected abstract AopService getAopService();
3. Use the replaced-method tag in the xml file, to inject the method from the author's second approach to the bean.
If we do it like that, we will get the bean totally unaware of the spring, the only think to remamber is to use the getAopService() method to invoke the managed methods.
The point is that we don't want to change class design just to be able to intercept all calls via Spring AOP. I.e. suggested approach makes target class implicitly aware about Spring.
ReplyDelete