I stumbled upon a case where the AOP proxy created by using @Cacheable breaks the dependency injection in Spring 3.1.1. Here is my scenario:
I have an interface and a class implementing this interface using @Cacheable at the implemented method.
Example interface:
public interface ImgService {
public byte[] getImage(String name);
}
Example implementation:
public class ImgServiceImpl implements ImgService {
@Cacheable(cacheName = "someCache")
public byte[] getImage(String name){//TODO};
protected String someOtherMethod(){//};
}
I also have to JUnit test classes - one which injects the interface and one the implementation:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring.xml" })
public class ImgServiceTest {
@Inject
private ImgService;
}
and
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring.xml" })
public class ImgServiceImplTest {
@Inject
private ImgServiceImpl;
}
Dependency injection for the interface works fine. However, when I get to injecting the implementation in the second test class I get an "Injection of autowired dependencies failed". I was able to debug it and it appears that ClassUtils.isAssignableValue() wrongly compares the desired type to the proxy class. It is called by DefaultListableBeanFactory. What is even stranger is that if I remove the @Cacheable annotation from the implemented method and add it to some other protected/private method, dependency injection works fine again. Is this a bug and what would be the correct approach to handle this situation?
OK, so here is the solution I came up finally. I implemented a simple method that attempts to extract the target object from the proxy based on its implementation of the org.springframework.aop.framework.Advised
class:
@SuppressWarnings({"unchecked"})
public static <T> T getTargetObject(Object proxy, Class<T> targetClass) {
if (AopUtils.isJdkDynamicProxy(proxy)) {
try {
return (T) ((Advised)proxy).getTargetSource().getTarget();
} catch (Exception e) {
return null;
}
} else {
return (T) proxy;
}
}
My implementation test class now looks like this:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring.xml" })
public class ImgServiceImplTest {
@Inject
private ImgService imgService;
private ImgServiceImpl imgServiceImpl;
@PostConstruct
public void setUp() {
imgServiceImpl = getTargetObject(imgService, ImgServiceImpl.class);
}
}