6

我偶然发现了使用@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;
}

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

4

2 回答 2

10

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

由于对 的可缓存方法的所有调用都ImgServiceImpl应该通过 type 的动态代理ImgService,因此无法将此依赖项注入到 type 的字段中ImgServiceImpl

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

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

另一个选择是将 Spring 配置为使用基于 AspectJ 的 AOP 实现(需要编译时或加载时编织)。

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

也可以看看:

于 2012-08-13T15:40:45.190 回答
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);
    }


}
于 2012-08-14T07:26:56.370 回答