Spring 4 不自动限定自动装配上的泛型类型

Spring 4 not automatically qualifying generic types on autowire

问题已确定,POST已更新(滚动到底部)

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

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

public interface DomainRepository<T> {

    T add(T entity) throws ServiceException, IllegalArgumentException;

    // ...etc

}

然后我有两个具体的实现,ChunkRepositoryImplProjectRepositoryImpl,它们被相应地参数化。它们共享抽象 class 中的一些通用实现,但声明如下:

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

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

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.foo.bar.repositories.DomainRepository] is defined: expected single matching bean but found 2: chunkRepositoryImpl,projectRepositoryImpl

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

更新

我找到问题的根源了。我的 DomainRepository<T> 接口中的方法之一被标记为 @Async,并利用了 Spring 的异步功能。删除它意味着 bean 是正确限定的。我假设 Spring 在幕后将带有 @Async 方法的 classes 转换为其他一些 class,并且这个过程剥离了类型信息,这意味着它无法告诉豆子分开。

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

  1. 这是有意为之的行为吗?
  2. 有人可以提出解决方法吗?

Here 是一个演示问题的项目。只需从 DomainRepository<T> 界面中删除 @Async 注释,问题就会消失。

I hypothesize that Spring transforms classes with @Async methods under the hood into some other class, and this process strips the type information, meaning that it can't tell the beans apart.

是的。这正是发生的事情。

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

@Autowired
private DomainRepository<Project> repository;

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

但是,在您的代码中,您实际上没有 ProjectRepositoryImpl 类型的 bean,甚至没有 DomainRepository<Project> 类型的 bean。您实际上有一个 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> 的类型描述符。

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

解决方案是使用 CGLIB 代理

@EnableAsync(proxyTargetClass = true)

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


上面的很多内容都是实现细节,在许多单独的地方、official documentation, javadoc、评论等地方都有定义。请谨慎使用它们。