Spring4.x泛型Service加@Validated后注入报NoUniqueBeanDefinitionException
问题解答
1. Spring基于泛型类型匹配注入的实现逻辑
Spring从4.3版本开始正式支持将泛型类型作为Bean的隐式匹配限定符,核心逻辑如下:
- Bean初始化阶段:Spring会解析每个实现类的完整类型签名,将泛型参数的实际类型封装为
ResolvableType对象,保存在对应Bean的定义信息中。比如UserSearchService<Email,UserDto>的ResolvableType会记录泛型K的实际类型为Email、V的实际类型为UserDto。 - 注入点解析阶段:当处理
@Autowired SearchService<Email,UserDto>这类带泛型的注入点时,Spring会先解析注入点的ResolvableType,拿到期望的泛型参数类型。 - 匹配阶段:Spring会遍历所有实现了
SearchService接口的Bean,逐一对比Bean的ResolvableType和注入点的ResolvableType,只有泛型参数完全匹配的Bean才会被选中注入,无需额外指定@Qualifier。
2. 添加@Validated注解后泛型匹配失效的原因及解决方案
失效原因
@Validated注解会触发Spring的AOP动态代理,你遇到的问题是JDK动态代理的泛型信息丢失导致的:
- Spring 4.3版本默认优先使用JDK动态代理生成AOP代理类,JDK代理只会实现目标类的接口,生成的代理类的泛型签名直接继承自接口
SearchService<K,V>,丢失了实现类的实际泛型参数信息,解析时得到的是SearchService<?,?>,无法和注入点的具体泛型匹配,因此抛出NoUniqueBeanDefinitionException。 - 同时JDK代理生成的类和
UserSearchService是平级的接口实现关系,没有继承关系,因此直接按UserSearchService.class类型从容器中获取Bean会提示不存在。 - 相同代码在Spring Boot中可正常运行,是因为Spring Boot 2.x及以上版本默认将AOP代理策略配置为CGLIB代理,CGLIB代理通过继承目标实现类生成代理,会保留实现类的泛型参数信息,因此泛型匹配逻辑可正常生效。
解决方案
可根据你的项目情况选择以下任意一种方案:
- 方案一:全局配置AOP使用CGLIB代理
在Spring配置中开启CGLIB代理,Java配置类添加@EnableAspectJAutoProxy(proxyTargetClass = true),XML配置添加<aop:config proxy-target-class="true"/>即可。该方案可保留实现类的泛型信息,同时支持直接按实现类类型从容器获取Bean,改动最小。 - 方案二:为实现类指定Bean名称,按名称匹配注入
给每个SearchService实现类的@Service注解指定名称,比如@Service("userSearchService"),注入时配合@Qualifier指定名称:
该方案无需修改全局代理配置,适合不能改动全局AOP策略的场景。@Autowired @Qualifier("userSearchService") private SearchService<Email, UserDto> userSearchService; - 方案三:升级Spring版本到5.x及以上
Spring 5.x对代理类的泛型解析逻辑做了优化,即使使用JDK动态代理,也会从代理的目标类中读取泛型参数信息,不会出现泛型丢失的问题。
内容的提问来源于stack exchange,提问作者zeroGone
相关产品推荐
相关产品推荐

