Spring 4不会自动限定autowire上的泛型类型

Wil*_*ull 1 java generics spring dependency-injection spring-4

问题已被识别,更新后(滚动到底部)

我正在开发一个桌面应用程序,目前使用Spring(spring-context,4.1.6.RELEASE)进行IoC和依赖注入.我正在使用注释配置@ComponentScan.我遇到的问题应该作为一个功能实现4.X.X,因为它在这里这里说明,但我得到了旧的3.X.X例外.

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

public interface DomainRepository<T> {

    T add(T entity) throws ServiceException, IllegalArgumentException;

    // ...etc

}
Run Code Online (Sandbox Code Playgroud)

然后,我有两个具体的实现,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

}
Run Code Online (Sandbox Code Playgroud)

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

@Autowired
private DomainRepository<Project> repository;
Run Code Online (Sandbox Code Playgroud)

我得到以下异常(当然是前面有一个很长的堆栈跟踪):

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

任何人都可以阐明为什么会发生这种情况?我希望这个例外3.X.X,但不应该发生4.X.X.我的情况和这里描述的情况有什么区别?

UPDATE

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

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

  1. 这是预期的行为吗?
  2. 任何人都可以建议一个解决方法吗?

是一个展示问题的项目.只需@AsyncDomainRepository<T>界面中删除注释,问题就会消失.

Sot*_*lis 5

我假设Spring将带有@Async方法的类转换为其他类,这个过程剥离了类型信息,这意味着它无法区分bean.

是.这正是发生的事情.

Spring 4支持通过完整的通用签名注入bean.鉴于注射目标

@Autowired
private DomainRepository<Project> repository;
Run Code Online (Sandbox Code Playgroud)

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

但是,在您的代码中,您实际上并没有类型的bean ProjectRepositoryImpl,甚至没有类型的bean DomainRepository<Project>.实际上,你有型的一个bean java.lang.Proxy(它实际上是一个动态的子类)实现DomainRepository,org.springframework.aop.SpringProxyorg.springframework.aop.framework.Advised.

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

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

解决方案是使用CGLIB代理

@EnableAsync(proxyTargetClass = true)
Run Code Online (Sandbox Code Playgroud)

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


以上是很多实现细节,并在许多单独的地方,官方文档,javadoc,注释等中定义.请谨慎使用它们.