这个 BeanNotOfRequiredTypeException 是一个绝佳的案例,它完美地揭示了 @Resource 和 @Autowired 核心行为模式的差异。
让我来为你深入剖析一下。
问题根源:注入策略的“一步之遥”
你的问题核心在于:
@Resource的策略是 “先按名称,再按类型”。@Autowired的策略是 “先按类型,再按名称”。
让我们一步一步拆解你的场景:
场景分析
你很可能在代码中这样写的:
@Resource
private StringRedisTemplate redisTemplate; // <-- 字段名是 "redisTemplate",类型是 "StringRedisTemplate"
同时,在你的 Spring/Spring Boot 环境中,容器里很可能存在两个 RedisTemplate 相关的 Bean:
- 一个是由 Spring Boot 自动配置的、名为
redisTemplate的 Bean,它的实际类型是RedisTemplate<Object, Object>。 - 另一个是由 Spring Boot 自动配置的、名为
stringRedisTemplate的 Bean,它的实际类型是StringRedisTemplate。
1. @Resource 的注入过程 (为什么失败)
-
第一步:按名称查找。
@Resource没有指定name属性,所以它会使用字段名redisTemplate作为要查找的 Bean 的名称。- 它向 Spring IoC 容器发出请求:“请给我一个名为
redisTemplate的 Bean”。 - Spring 容器很爽快地找到了!就是我们上面说的第一个 Bean,那个类型为
RedisTemplate<Object, Object>的实例。
-
第二步:类型检查。
@Resource拿到了这个名为redisTemplate的 Bean,准备将它注入到你的字段中。- 在注入前,它会进行一次类型安全检查:“我手里的这个
RedisTemplate<Object, Object>实例,可以赋值给StringRedisTemplate类型的字段吗?” - 答案是:不行!
StringRedisTemplate是RedisTemplate的一个特定子类 (StringRedisTemplate extends RedisTemplate<String, String>)。你不能把一个父类(或泛型更宽泛的类)的实例赋值给一个子类引用。这就好比你不能执行Dog dog = new Animal();。 - 于是,Spring 抛出
BeanNotOfRequiredTypeException。异常信息说得非常清楚:“名为 ‘redisTemplate’ 的 Bean,期望的类型是 ‘StringRedisTemplate’,但实际类型是 ‘RedisTemplate’”。它准确地描述了上面发生的整个过程。
2. @Autowired 的注入过程 (为什么成功)
-
第一步:按类型查找。
@Autowired首先关心的是字段的类型,即StringRedisTemplate。- 它向 Spring IoC 容器发出请求:“请给我所有类型是
StringRedisTemplate(或其子类) 的 Bean”。 - Spring 容器在内部查找,找到了我们上面提到的第二个 Bean,那个名为
stringRedisTemplate的 Bean,它的类型正好是StringRedisTemplate。
-
第二步:处理候选者。
- 假设容器中只有一个
StringRedisTemplate类型的 Bean(在标准的 Spring Boot 配置下就是如此),@Autowired发现候选者唯一。 - 它直接将这个唯一的
StringRedisTemplate实例注入到你的字段中。 - 注入成功!注意,在这个过程中,
@Autowired根本没有使用到redisTemplate这个字段名。
- 假设容器中只有一个
如何修正并正确使用 @Resource?
你有两种非常清晰的修正方式,这也能加深你对 @Resource 的理解:
方案一:让字段名和 Bean 名称一致(推荐)
这是最符合 @Resource 设计哲学的方式。既然你要注入的是 StringRedisTemplate,就把字段名改成它在容器中的默认名字。
// Spring 容器中 StringRedisTemplate 类型的 Bean 默认名字是 stringRedisTemplate
@Resource
private StringRedisTemplate stringRedisTemplate; // 字段名和 Bean 名完全匹配,一步到位
// 使用时
stringRedisTemplate.opsForValue().set("hello", "world");
方案二:明确使用 name 属性指定 Bean 名称
如果因为某些代码规范,你不想修改字段名,可以明确告诉 @Resource 你要注入的 Bean 的名字。
@Resource(name = "stringRedisTemplate") // 明确指定要注入名为 "stringRedisTemplate" 的 Bean
private StringRedisTemplate redisTemplate; // 字段名可以保持不变
// 使用时
redisTemplate.opsForValue().set("hello", "world");
结论与最佳实践
这个案例生动地说明了:
@Resource的优势在于其确定性:它默认通过名称来定位,意图非常明确,可以避免在有多个同类型 Bean 时产生歧义。@Autowired的优势在于其便利性:在大多数“一个接口,一个实现”的场景下,它只关心类型,写起来更简单,也更利于重构(比如修改字段名不影响注入)。- 理解你的工具箱:当你的环境中存在多个相似类型(如
RedisTemplate和StringRedisTemplate)的 Bean 时,必须清楚你正在使用的注解的底层工作原理,才能准确地注入你想要的那个。
总而言之,你的 @Resource 并没有“不能用”,而是它的工作方式完全按照规范执行,并准确地通过异常告诉了你问题所在。当你理解了它的“按名称优先”原则后,这个问题便迎刃而解。
456

被折叠的 条评论
为什么被折叠?



