Search code examples
javaspringdependency-injectionannotationsproxy-classes

@Cacheable breaks DependencyInjection


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?


Solution

  • 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);
        }
    
    
    }