Spring 4 不自动限定自动装配上的泛型类型
Spring 4 not automatically qualifying generic types on autowire
问题已确定,POST已更新(滚动到底部)
我正在开发一个桌面应用程序,目前使用 Spring(spring-context
、4.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
}
然后我有两个具体的实现,ChunkRepositoryImpl
和 ProjectRepositoryImpl
,它们被相应地参数化。它们共享抽象 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,并且这个过程剥离了类型信息,这意味着它无法告诉豆子分开。
这意味着我现在有两个问题:
- 这是有意为之的行为吗?
- 有人可以提出解决方法吗?
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(实际上是它的动态子类),它实现了 DomainRepository
、org.springframework.aop.SpringProxy
和 org.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、评论等地方都有定义。请谨慎使用它们。
问题已确定,POST已更新(滚动到底部)
我正在开发一个桌面应用程序,目前使用 Spring(spring-context
、4.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
}
然后我有两个具体的实现,ChunkRepositoryImpl
和 ProjectRepositoryImpl
,它们被相应地参数化。它们共享抽象 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,并且这个过程剥离了类型信息,这意味着它无法告诉豆子分开。
这意味着我现在有两个问题:
- 这是有意为之的行为吗?
- 有人可以提出解决方法吗?
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(实际上是它的动态子类),它实现了 DomainRepository
、org.springframework.aop.SpringProxy
和 org.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、评论等地方都有定义。请谨慎使用它们。