@Cacheable打破了DependencyInjection

时间:2012-08-13 15:22:08

标签: java spring dependency-injection annotations proxy-classes

我偶然发现了一个案例,即使用@Cacheable创建的AOP代理在Spring 3.1.1中打破了依赖注入。这是我的情景:

我有一个接口和一个使用@Cacheable在实现的方法中实现此接口的类。

示例界面:

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

示例实施:

public class ImgServiceImpl implements ImgService {

    @Cacheable(cacheName = "someCache")
    public byte[] getImage(String name){//TODO};

    protected String someOtherMethod(){//};
}

我还需要JUnit测试类 - 一个注入接口,另一个注入实现:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring.xml" })
public class ImgServiceTest {

    @Inject
    private ImgService;
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring.xml" })
public class ImgServiceImplTest {

    @Inject
    private ImgServiceImpl;
}

接口的依赖注入工作正常。但是,当我在第二个测试类中注入实现时,我得到了一个"注入自动连接的依赖项失败" 。我能够调试它,似乎 ClassUtils.isAssignableValue()错误地将所需类型与代理类进行比较。它由 DefaultListableBeanFactory 调用。更奇怪的是,如果我从已实现的方法中删除@Cacheable注释并将其添加到其他一些受保护/私有方法,依赖注入再次正常工作。这是一个错误,处理这种情况的正确方法是什么?

2 个答案:

答案 0 :(得分:10)

这不是一个错误,这是使用JDK动态代理进行AOP实现的预期副作用。

由于对ImgServiceImpl的可缓存方法的所有调用都应该通过类型为ImgService的动态代理,因此无法将此依赖项注入类型为ImgServiceImpl的字段中。< / p>

当您将@Cacheable移至privateprotected方法时,注入有效,因为@Cacheable在这种情况下不起作用 - 只有public方法可以建议使用基于代理的AOP。

因此,您应该声明要注入ImgService的字段,或者使用proxy-target-class = "true"将Spring配置为使用基于目标类的代理。

另一个选择是将Spring配置为使用AspectJ-based AOP implementation(需要编译时或加载时编织)。

它适用于Spring提供的所有基于AOP的功能(事务,安全性,异步执行,缓存,自定义方面等)。

另见:

答案 1 :(得分:3)

好的,所以这是我最终提出的解决方案。我实现了一个简单的方法,尝试根据org.springframework.aop.framework.Advised类的实现从代理中提取目标对象:

@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;
  }
}

我的实现测试类现在看起来像这样:

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


}