用@Resource报org.springframework.beans.factory.BeanNotOfRequiredTypeException

这个 BeanNotOfRequiredTypeException 是一个绝佳的案例,它完美地揭示了 @Resource@Autowired 核心行为模式的差异。

让我来为你深入剖析一下。

问题根源:注入策略的“一步之遥”

你的问题核心在于:

  • @Resource 的策略是 “先按名称,再按类型”
  • @Autowired 的策略是 “先按类型,再按名称”

让我们一步一步拆解你的场景:

场景分析

你很可能在代码中这样写的:

@Resource
private StringRedisTemplate redisTemplate; // <-- 字段名是 "redisTemplate",类型是 "StringRedisTemplate"

同时,在你的 Spring/Spring Boot 环境中,容器里很可能存在两个 RedisTemplate 相关的 Bean:

  1. 一个是由 Spring Boot 自动配置的、名为 redisTemplate 的 Bean,它的实际类型是 RedisTemplate<Object, Object>
  2. 另一个是由 Spring Boot 自动配置的、名为 stringRedisTemplate 的 Bean,它的实际类型是 StringRedisTemplate
1. @Resource 的注入过程 (为什么失败)
  1. 第一步:按名称查找。

    • @Resource 没有指定 name 属性,所以它会使用字段名 redisTemplate 作为要查找的 Bean 的名称。
    • 它向 Spring IoC 容器发出请求:“请给我一个名为 redisTemplate 的 Bean”。
    • Spring 容器很爽快地找到了!就是我们上面说的第一个 Bean,那个类型为 RedisTemplate<Object, Object> 的实例。
  2. 第二步:类型检查。

    • @Resource 拿到了这个名为 redisTemplate 的 Bean,准备将它注入到你的字段中。
    • 在注入前,它会进行一次类型安全检查:“我手里的这个 RedisTemplate<Object, Object> 实例,可以赋值给 StringRedisTemplate 类型的字段吗?”
    • 答案是:不行! StringRedisTemplateRedisTemplate 的一个特定子类 (StringRedisTemplate extends RedisTemplate<String, String>)。你不能把一个父类(或泛型更宽泛的类)的实例赋值给一个子类引用。这就好比你不能执行 Dog dog = new Animal();
    • 于是,Spring 抛出 BeanNotOfRequiredTypeException。异常信息说得非常清楚:“名为 ‘redisTemplate’ 的 Bean,期望的类型是 ‘StringRedisTemplate’,但实际类型是 ‘RedisTemplate’”。它准确地描述了上面发生的整个过程。
2. @Autowired 的注入过程 (为什么成功)
  1. 第一步:按类型查找。

    • @Autowired 首先关心的是字段的类型,即 StringRedisTemplate
    • 它向 Spring IoC 容器发出请求:“请给我所有类型是 StringRedisTemplate (或其子类) 的 Bean”。
    • Spring 容器在内部查找,找到了我们上面提到的第二个 Bean,那个名为 stringRedisTemplate 的 Bean,它的类型正好是 StringRedisTemplate
  2. 第二步:处理候选者。

    • 假设容器中只有一个 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");

结论与最佳实践

这个案例生动地说明了:

  1. @Resource 的优势在于其确定性:它默认通过名称来定位,意图非常明确,可以避免在有多个同类型 Bean 时产生歧义。
  2. @Autowired 的优势在于其便利性:在大多数“一个接口,一个实现”的场景下,它只关心类型,写起来更简单,也更利于重构(比如修改字段名不影响注入)。
  3. 理解你的工具箱:当你的环境中存在多个相似类型(如 RedisTemplateStringRedisTemplate)的 Bean 时,必须清楚你正在使用的注解的底层工作原理,才能准确地注入你想要的那个。

总而言之,你的 @Resource 并没有“不能用”,而是它的工作方式完全按照规范执行,并准确地通过异常告诉了你问题所在。当你理解了它的“按名称优先”原则后,这个问题便迎刃而解。

Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'authenticationManager' is expected to be of type 'org.springframework.security.authentication.AuthenticationManager' but was actually of type 'org.springframework.beans.factory.support.NullBean' at org.springframework.beans.factory.support.AbstractBeanFactory.adaptBeanInstance(AbstractBeanFactory.java:418) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:399) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:214) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:479) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:554) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:524) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:677) at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:228) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:329) ... 22 common frames omitted
08-22
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值