Spring 4不会自动验证autowire

时间:2015-10-06 22:10:59

标签: java spring generics dependency-injection spring-4

问题已经过识别,更新后发布(滚动到底部)

我正在开发一个桌面应用程序,目前使用Spring(spring-context4.1.6.RELEASE)进行IoC和依赖注入。我正在使用注释配置,使用@ComponentScan。我遇到的问题应该作为4.X.X中的一项功能实施,因为它指出了herehere,但我得到了旧3.X.X例外。

我有一个参数化接口,代表一个通用存储库:

public interface DomainRepository<T> {

    T add(T entity) throws ServiceException, IllegalArgumentException;

    // ...etc

}

然后,我有两个具体的实现,ChunkRepositoryImplProjectRepositoryImpl,相应地进行参数化。它们从抽象类中共享一些常见的实现,但声明如下:

@Repository
public class ChunkRepositoryImpl extends AbstractRepositoryImpl<Chunk> implements DomainRepository<Chunk> {

    // ...+ various method implementations

}

@Repository
public class ProjectRepositoryImpl extends AbstractRepositoryImpl<Project> implements DomainRepository<Project> {

    // ...+ various method implementations

}

我对上述链接的理解使我相信我应该能够自动装配这些链接而无需通过@Qualifier手动指定bean。但是,当我这样做时:

@Autowired
private DomainRepository<Project> repository;

我得到以下异常(当然是在长堆栈跟踪之前):

  

引起:org.springframework.beans.factory.NoUniqueBeanDefinitionException:没有定义[com.foo.bar.repositories.DomainRepository]类型的限定bean:期望的单个匹配bean但找到2:chunkRepositoryImpl,projectRepositoryImpl

任何人都可以阐明为什么会发生这种情况吗?我希望在3.X.X中出现此异常,但不应该在4.X.X中发生。我的情况和描述here的情况有什么区别?

更新

我发现了问题的根源。我的DomainRepository<T>接口中的一个方法标记为@Async,并使用了Spring的异步功能。删除这意味着bean已正确限定。我假设Spring将带有@Async方法的类转换为其他类,这个过程会删除类型信息,这意味着它无法区分bean。

这意味着我现在有两个问题:

  1. 这是预期的行为吗?
  2. 有人可以建议解决方法吗?
  3. Here是一个展示问题的项目。只需从@Async界面中删除DomainRepository<T>注释,问题就会消失。

1 个答案:

答案 0 :(得分:5)

  

我假设Spring使用@Async方法转换类   引擎盖进入其他一些类,这个过程剥离了类型   信息,这意味着它无法区分豆类。

是。这正是发生的事情。

Spring 4支持通过完整的通用签名注入bean。给定注射目标

@Autowired
private DomainRepository<Project> repository;

和一个类型为ProjectRepositoryImpl 的bean ,Spring将正确解析并将该bean注入字段(或方法参数或构造函数参数)。

然而,在您的代码中,您实际上并没有ProjectRepositoryImpl类型的bean,甚至类型DomainRepository<Project>也没有。实际上,您实现了java.lang.Proxy类型的bean(实际上是它的动态子类),它实现了DomainRepositoryorg.springframework.aop.SpringProxyorg.springframework.aop.framework.Advised

使用@Async,Spring需要代理您的bean以添加异步调度行为。默认情况下,此代理是JDK代理。 JDK代理只能继承目标类型的接口。 JDK代理使用工厂方法Proxy#newProxyInstance(...)生成。请注意它只接受Class个参数,而不是Type。因此,它只能接收DomainRepository的类型描述符,而不能接收DomainRepository<Chunk>的类型描述符。

因此,您没有实现参数化目标类型DocumentRepository<Project>的bean。 Spring将回退到原始类型DocumentRepository并找到两个候选bean。这是一个模棱两可的比赛,所以它失败了。

解决方案是使用CGLIB代理

@EnableAsync(proxyTargetClass = true)

CGLIB代理允许Spring获取完整的类型信息,而不仅仅是接口。因此,您的代理实际上会有一个类型为ProjectRepositoryImpl的子类型,例如,它带有DocumentRepository<Project>类型信息。

以上很多都是实施细节,并在许多不同的地方定义,official documentationjavadoc,评论等。请谨慎使用。