Spring 的核心是 Ioc 容器和 DI(Dependence Injection)思想,这些提供了 java 对象的管理以及他们之间依赖的管理。bean 的管理是 Spring 自动管理的,而我们只需要使用一些注解(Annotion)。
常用的是 @Resource 和 @Autowired 以及 lambok 的构造器进行注入。
使用
注入前对象必须由 Spring 管理。通过 @Component、@Service、@Repository 标识,Springboot 会自动扫描并初始化 Bean。
@Autowired
1 @Autowired
2 private UserMapper userMapper;
如果 Bean 出现了重复,在项目中通常使用@Qualifier 单独指定 bean. 注意: @Qualifier 不能单独使用只能配合@Autowired 使用。
1 @Autowired
2 @Qualifier(value = "userMapper")
3 private UserMapper userMapper;
@Autowired 可以放在成员变量(field),setter、类构造器。它只有一个属性 —— required,Boolean 类型,取值为 false 时不依赖 bean,也就是说被注入 field 可以为 null,否则当依赖 bean 不存在时报错。
1// Autowired 构造器注入
2class UserServiceImpl{
3 private UserMapper userMapper;
4 private DeptMapper deptMapper;
5
6 @Autowired
7 public UserServiceImpl(UserMapper userMapper, DeptMapper deptMapper){
8 this.userMapper = userMapper;
9 this.deptMapper = deptMapper;
10 }
11}
12
13// Autowired Setter 注入
14class UserServiceImpl{
15 private UserMapper userMapper;
16
17 @Autowired
18 public void setUserMapper(UserMapper userMapper){
19 this.userMapper = userMapper;
20 }
21}
@Resource
@Resource 是 java 规范的一个注解,Spring 也支持它,因此在 spring 项目中可以用它。
@Resource 是默认按照类型的名字注入 bean,这个名字通常是类名(其第一个字母小写);它也可以通过类型匹配,通过哪种类型匹配取决于 name 和 type 两个属性,两个不同时出现,同时出现按默认走。
当@Resource 匹配不到 Bean 时就不再注入,此时 field 为 null。
1 // 按名字注入bean
2 @Resource(name = "userMapper")
3 private UserMapper userMapper;
4
5 // 按类型注入备案
6 @Resource(type = UserMapper.class)
7 private UserMapper userMapper;
在项目中 @Autowired 已经不推荐使用了, 通常推荐使用 JSR-250 @Resource,这样可以减少对 Spring 的依赖,让代码更加规范统一。
另外,和@Resource 相关的还有 @PostConstruct 以、@PreDestroy。
- @PostConstruct 相当于 init-method,使用在方法上,当 Bean 初始化时执行。
- @PreDestroy 相当于 destory-method,使用在方法上,当 Bean 销毁时执行。
@Autowired 和 @Resource 的区别
- @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解
- @Autowired 默认通过被注入对象的类型注入,而@Resource 默认通过类型注入
- @Autowired 与@Resource 都可以用来装配 bean. 都可以写在字段上或写在 setter 方法上
@RequiredArgsConstructor 构造器注入
lambok 的构造器注入需要使用 @RequiredArgsConstructor 注解, 放在类上。
简单使用示例
1@RequiredArgsConstructor
2@Service
3public class UserServiceImpl{
4 private final UserMapper userMapper;
5}
注意事项
- 类需要注入的字段(fields)都需要 final 修饰
- 如不用 final 修饰,或已经赋值常量,则 spring 不进行注入,这些字段通常是类中的定义的静态常量, 如下
1@RequiredArgsConstructor
2public class UserServiceImpl{
3 // 注入
4 private final UserMapper userMapper;
5 // 不注入
6 private static String USER_SIGN = "admin"
7 // 不注入
8 private final static String USER_SIGN2 = "user"
9}
- 使用构造器注入时,在使用对象就不能推荐 new 了,new 对象时调用对象构造器,因为对象的 field 是通过构造器注入的,new 时所有需要注入的对象都需要填在构造器里,当对象需要注入的 bean 多时是比较麻烦的一件事。
经常遇到的问题
常见 Spring 注入 Bean 为 null
- 首先排查 bean 是否被扫描到,Springboot 项目中与启动类同级及一下的 bean 才能被扫描到; 引入 mybatis 或 mybatis plus 所有模块的 Mapper 都需要使用@MapperScan 进行扫描,否则需要使用 xml 配置扫描位置(不推荐这种方法)。
- 如果 bean 装配没有问题,那可能这个对象是 new 出来的,这时不对 new 的对象中的@Autowired @Resource 标识的进行注入。
- bean 出现重复,这种情况控制台会报错,设置并在注入的时候通过@Qualifier 或@Resource(name = “xxx”)指明具体的 bean 即可
- 另一种解决办法,通过 SpringContext 拿,方法如下
1@Slf4j
2@Configuration
3public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
4
5 private static ApplicationContext applicationContext = null;
6
7 /**
8 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
9 */
10 @SuppressWarnings("unchecked")
11 public static <T> T getBean(String name) {
12 assertContextInjected();
13 return (T) applicationContext.getBean(name);
14 }
15
16 /**
17 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
18 */
19 public static <T> T getBean(Class<T> requiredType) {
20 assertContextInjected();
21 return applicationContext.getBean(requiredType);
22 }
23
24 @Override
25 public void destroy() {
26 SpringContextHolder.clearHolder();
27 }
28
29 @Override
30 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
31 if (SpringContextHolder.applicationContext != null) {
32 log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
33 }
34 SpringContextHolder.applicationContext = applicationContext;
35 }
36}
另一种情况注入对象为 null 的情况
在静态代码块/普通代码块使用被注入对象,被注入的对象为 null。
这种情况代码:
1@Component("functionExecutor")
2public class FunctionExecutor {
3
4 @Resource(name = "whereGreaterThenFunction")
5 private WhereGreaterThenFunction whereGreaterThenFunction;
6
7 @Resource(name = "countFunction")
8 private CountFunction countFunction;
9
10 // 静态/普通代码
11 {
12 AviatorEvaluator.addFunction(whereGreaterThenFunction);
13 AviatorEvaluator.addFunction(countFunction);
14 }
15}
这里注入不成功是因为,在初始化 FunctionExecutor 时,成员变量还没有被注入,此时对象为 null。
解决办法是使用 @PostConstruct ,使用这个注解的方法会在 bean 初始化完成后被调用,这时所有的成员变量都已经注入。
最佳实践
- 在使用@Component @Service @Controller @Repository 通常不指定 bean 的名字, 除非有重名的
- 注入 bean 时推荐使用 lombok 的构造器注入,更加简洁
- 单独注入 bean 推荐使用@Resource 而不是@Autowired