今天项目上线后出现一个生产问题, @Resource 注解注入 systemTaskService 对象失败, 导致对象空指针了
但是本次修改内容修改了很多地方, 都使用 @Resource 注解引用了该对象, 然而只有这一处注入失败, 其他位置都正常…
这就很奇怪了,修改内容都一样,所以只测了几个地方没有问题就认为可以了,坑啊…这是为啥呢?
好奇的我就复习了一下 @Resource 和 @Autowired 的区别
共同点
@Resource 和 @Autowired 都可以作为注入属性的修饰
在接口仅有单一实现类时, 两个注解的修饰效果相同, 可以互相替换, 不影响使用
示例 1
package com.example.annotation.service;
/**
* service接口定义
* @author Administrator
*/
public interface Human {
/**
* 跑马拉松
* @return
*/
String runMarathon();
}
package com.example.annotation.service.impl;
import com.example.annotation.service.Human;
import org.springframework.stereotype.Service;
/**
* service接口第一实现类
* @author Administrator
*/
@Service
public class Man implements Human {
public String runMarathon() {
return "A man run marathon";
}
}
package com.example.annotation.controller;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.annotation.service.Human;
/**
* controller层实现类
* @author Administrator
*/
@RestController
@RequestMapping("/an")
public class HumanController {
@Resource
private Human human;
@RequestMapping("/run")
public String runMarathon() {
return human.runMarathon();
}
}
至此,代码整理完成,启动后输出 A man run marathon
将 HumanController.java 类中的注解替换为 @Autowired, 再次启动, 仍然输出 A man run marathon
在接口仅有单一实现类时, 两个注解的修饰效果相同, 可以互相替换, 不影响使用
不同点
@Resource 是 Java 自己的注解
@Resource 有两个属性是比较重要的,分是 name 和 type
Spring 将 @Resource 注解的 name 属性解析为 bean 的名字, 而 type 属性则解析为 bean 的类型:
- 如果使用
name属性, 则使用byName的自动注入策略 - 如果使用
type属性, 则使用byType自动注入策略 - 如果既不指定
name也不指定type属性, 这时将通过反射机制使用byName自动注入策略
示例 2
再次修改示例 1 的代码, 增加一个实现类 Woman.java, HumanController.java 注解仍然使用 @Resource
package com.example.annotation.service.impl;
import com.example.annotation.service.Human;
import org.springframework.stereotype.Service;
/**
* service接口第二实现类
* @author Administrator
*/
@Service
public class Woman implements Human {
public String runMarathon() {
return "An woman run marathon";
}
}
此时启动项目, 控制台会报错, 报错信息太多, 截取关键信息 expected single matching bean but found 2: man,woman:
2021-03-06 21:22:22.222 WARN 2222 --- [ restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'humanController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.annotation.service.Human' available: expected single matching bean but found 2: man,woman
期望的单一结果被匹配到两个结果 man 和 woman
在接口有多个实现类时, 就需要额外的处理才能正常使用
需要指定 @Resource 注解的 name 属性或使用 @Qualifier 来确定用哪一个实现类
HumanController.java 代码修改为下面任意一种代码, 指定 Human 接口的实现类是 Woman.java
@Resource(name="woman")
private Human human;
或
@Resource
@Qualifier("woman")
private Human human;
再次启动程序, 输出内容为 An woman run marathon
@Autowired 是 Spring 的注解
@Autowired 是 Spring 2.5 版本引入的, Autowired 只根据 type 进行注入, 不会去匹配 name
如果涉及到 type 无法辨别注入对象时, 那需要依赖 @Qualifier 或 @Primary 注解一起来修饰
示例 3
将示例 2 中的注解替换为 @Autowired:
@Autowired
private Human human;
再次启动时报错:
Description:
/source/_posts/ResourceAndAutowired.md
Field human in com.example.annotation.controller.HumanController required a single bean, but 2 were found:
- man: defined in file [/Users/baojunjie/CrazyProjects/Annotation/target/classes/com/example/annotation/service/impl/Man.class]
- woman: defined in file [/Users/baojunjie/CrazyProjects/Annotation/target/classes/com/example/annotation/service/impl/Woman.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
报错信息很明显, HumanController.java 需要一个 bean 实现, 但是找到了两个: man 和 woman
两种解决方案:
- 使用
@Primary注解, 在有多个实现bean时告诉 Spring 首先用@Primary修饰的那个 - 使用
@Qualifier来标注需要注入的类
其中 @Qualifier 修改方式与示例 2 相同, 依然是修改 HumanController.java 中间注入的 Human 上面
@Primary 是修饰实现类的
告诉 Spring, 如果有多个实现类时, 优先注入被 @Primary 注解修饰的那个
这里, 我们希望注入 Man.java, 那么修改 Man.java 为:
package com.example.annotation.service.impl;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import com.example.annotation.service.Human;
/**
* service接口第一实现类
* @author Administrator
*/
@Service
@Primary
public class Man implements Human {
public String runMarathon() {
return "A man run marathon";
}
}
再次启动应用, 就能看到正确的输出了 A man run marathon
然而
然而, 重新看了它俩的区别后,还是没能解答我这个问题到底为什么…


