Spring IoC 注入方式探究
1 先说问题
Spring IoC有哪几种注入方式?
网上的资料真的很多,但也真啥样的都有,我也不保证自己说的全部都是对的,但是提供一种思考的角度也是好的,望斧正。
在犹豫不决的时刻,是时候祭出官方文档和源码啦!带着问题看文档和源码,才能真正吃透一个知识点。
2 配置方式
首先IoC的容器配置方式主要分为两种:
- XML配置 - XML-based configuration
- 注解配置 - Annotation-based Container Configuration
配置方式只是我们使用的时候,告诉Spring去哪找资源而已,并不是所谓的“IoC注入方式”。去看文档,在Annotation-based Container Configuration章节,开篇就对比两种配置方式的优劣。
image.png翻译过来,简单点说,就是Spring对两种方式都可以容纳,不是说一个项目里非此即彼,只能使用其中一种。注释方式在用起来更短、更简洁,但是让人觉得配置分散;XML擅长在不接触源代码或不重新编译它们的情况下连接组件。
注意:注释注入会在XML注入之前执行。因此,如果同一个类的依赖通过两种方法都配置了注入,XML配置会覆盖注释配置的。
3 注入方式
注入方式我们可以说有以下四种,但实际上XML配置方式只实现前两种,后面两种用注解实现去做更方便。
- 构造器注入
- setter注入
- field注入
- 自定义的方法注入
传说还有一种接口注入的方式,但是在官方文档上已经找不到踪迹了。
3.1 构造器注入
Constructor-based Dependency Injection, 这是官方最推崇的注入方式,也是最便于进行单元测试的方式。但是当构造器中的参数过多,就显得很难受了,要考虑重构。
3.1.1 XML 配置的构造器注入
package x.y;
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private MovieFinder movieFinder;
// 提供给 Spring container 注入 MovieFinder 的构造器
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 省略其他业务逻辑
}
xml配置的时候大概长这样,在xml里, ref 标签表示你想要向一个对象传递一个引用。
<beans>
<bean id="simpleMovieLister" class="x.y.SimpleMovieLister">
<constructor-arg ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="x.y.MovieFinder"/>
</beans>
3.1.2 注解配置的构造器注入
注解我们就拿@Autowire
来举例,其实还可以使用@Resource
,@Inject
等注解。@Autowire
注解写在构造方法上,是在doCreateBean方法中的createBeanInstance时候注入的。
@Service
public class MovieRecommenderService {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommenderService(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
MovieRecommenderService
类一般情况下我们都会在类名上写@Service交由容器作为单例储存起来,不用自己去XML里费劲写<bean>
了。
非SpringBoot项目还需要在XML配置文件中使用<context:annotation-config/>标签开启自动注解。
3.2 Setter注入
3.2.1 XML配置的Setter注入
package x.y;
public class SimpleMovieLister {
// SimpleMovieLister 依赖于 MovieFinder
private MovieFinder movieFinder;
// 提供给容器进行注入的 setter 方法
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// 其他业务逻辑
}
xml的配置大概长这样
<bean id="simpleMovieLister" class="x.y.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
</bean>
<bean id="movieFinder" class="x.y.MovieFinder">
</bean>
3.2.2 注解配置的Setter注入
@Autowired放在Setter方法上时,是装配属性的时候才注入,即调用doCreateBean方法中的populateBean
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
3.3 Field注入
这里进行Field与构造函数混合注入,事实上我们看到很多的项目,都是只使用的Field注入,这样做的问题在于,单元测试的时候想要注入Mock的依赖就很麻烦。
@Autowired放在Field上时,是装配属性的时候才注入,即调用doCreateBean方法中的populateBean
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
3.4 自定义的方法注入
emmm...暂时还没见过哪个项目里是这么写的
如果@Autowire写在自定义的方法上,也是装配属性的时候才注入,doCreateBean方法中的populateBean
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}