@Cacheable breaks DependencyInjection dependency

I came across a case where an AOP proxy created using @Cacheable breaks dependency injection in Spring 3.1.1. Here is my scenario:

I have an interface and a class that implements this interface using @Cacheable with the implemented method.

Interface example:

public interface ImgService { public byte[] getImage(String name); } 

Implementation Example:

 public class ImgServiceImpl implements ImgService { @Cacheable(cacheName = "someCache") public byte[] getImage(String name){//TODO}; protected String someOtherMethod(){//}; } 

I also need the JUnit testing classes - one that introduces the interface, and the other an 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; } 

Injection for the interface works fine. However, when I receive an injection of the implementation in the second test class, I get the message "Injection of autwired dependencies failed". I managed to debug it, and it turned out that ClassUtils.isAssignableValue () incorrectly compares the desired type with the proxy class. It is called by default, ListableBeanFactory. Even stranger is that if I remove the @Cacheable annotation from the implemented method and add it to another protected / private method, dependency injection will stop again. Is this a mistake and what will be the correct approach to solving this situation?

+6
source share
2 answers

OK, so here is the solution I finally came up with. I applied a simple method that tries to retrieve the target 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 runtime testing 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); } } 
+3
source

This is not a mistake, it is an expected side effect of using dynamic JDK proxies to implement AOP.

Since all calls to the ImgServiceImpl caching ImgServiceImpl must pass through a dynamic proxy of type ImgService , it is impossible to insert this dependency into a field of type ImgServiceImpl .

When you move the @Cacheable method to private or protected , the injection works because @Cacheable does not work in this case - only public methods can be recommended using proxy-based AOP.

So, you must either declare the input fields as ImgService , or configure Spring instead of using class-based target proxies using proxy-target-class = "true" .

Another option is to configure Spring to use the AspectJ-based AOP implementation (requires compile time or load time).

It is applicable to all AOP-based functions provided by Spring (transactions, security, asynchronous execution, cache, user aspects, etc.).

See also:

+10
source

Source: https://habr.com/ru/post/922742/


All Articles