文章示例环境配置信息
jdk版本:1.8
开发工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE
前言
在Spring依赖注入(一):字段注入的方式是如何工作的?中主要分享了Spring bean依赖注入方式中的字段注入方式及其工作过程,这篇文章来分享一下SPrng bean依赖注入的第二种方式:setter方法注入,从名字来猜也能猜出一个大概,就是通过调用bean的setter方法进行注入,根本原理是java的反射技术。
settter注入方法
以@Autowired注解为例,即把@Autowired注解标记在目标bean的引用bean的setter方法上;
setter注入示例
示例主要内容:1、定义Teachert类; 2、定义Student类;3、在Student类中依赖Teacher;4、使用@Autowired注解标记在Student#setTeacher()上,即在Student对象中以setter方法注入的方式注入Teacher对象;
@Slf4j
@Component
public class Teacher {
private String name = "李老师";
private Student student;
public Teacher() {
log.info("----teacher的无参数构造方法被执行");
}
public Teacher(Student student) {
this.student = student;
log.info("----teacher的有参数构造方法被执行");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
log.info("----teacher中的setStudent方法被调用");
this.student = student;
}
}
@Slf4j
@Component
public class Student {
private String name = "小明";
private Teacher teacher;
public Student() {
log.info("----student的无参数构造方法被执行");
}
public Student(Teacher teacher) {
this.teacher = teacher;
log.info("----student的有参数构造方法(teacher)被执行");
}
public Student(String name, Teacher teacher) {
this.name = name;
this.teacher = teacher;
log.info("----student的有参数构造方法(name,teacher)被执行");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
@Autowired
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
log.info("----student中的setTeacher方法被调用");
}
}
@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("student")) {
Student student = (Student) bean;
log.info("----student属性注入完成,student.name:" + student.getName());
}
return bean;
}
}
@Component
@Slf4j
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (beanName.equals("student")||beanName.equals("teacher")) {
log.info("----bean实例化完成,beanName:" + beanName);
}
return true;
}
}
@Test
public void test4() {
log.info("----单元测试执行开始");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
Student bean = context.getBean(Student.class);
log.info("----bean:"+bean.getName());
log.info("----单元测试执行完毕");
}
单元测试执行日志:
![](https://i-blog.csdnimg.cn/blog_migrate/a217e32d639b14ae70320c88f9a0fd4c.png)
从执行日志来看,setter方式的依赖注入和字段注入方式基本流程是一样的,student先调用无参数的构造方法进行实例化,然后在student对象内需要注入teacher对象,发现teacher对象未实例化,于是开始实例化teacher对象,待teacher对象实例化完成后(teacher对象内无引用其他bean,其属性注入、bean初始化略过),teacher对象注册到Spring容器中后,又接着student对象实例化后的后续操作,调用student的setTeacher()进行teacher对象的注入。
setter注入的工作原理
1、AbstractAutowireCapableBeanFactory#doCreateBean--->createBeanInstance(),开始student对象的实例化;
![](https://i-blog.csdnimg.cn/blog_migrate/4456f4dc8de00697db6536fec00cb0ba.png)
2、AbstractAutowireCapableBeanFactory#createBeanInstance()执行完之后,student对象实例化完成,接着往下,在开始依赖属性注入之前,student对象提前暴露至Spring的第三级缓存里,这个时间很重要,也是解决bean循环依赖的关键所在,这里先加深一下印象,后面再细说。另外这里注意一下缓存的key是beanName,但是value不是一个具体的对象,而用lambda表达式写的接口ObjectFactory,ObjectFactory接口里有一个抽象方法getObject(),这里是什么意思呢?ObjectFactory#getObject()方法被触发的时候,会执行lambda表达式的内容; 而lambda表达式里的getEarlyBeanReference()正是从三级缓存里取完成实现化、未属性注入的半成品bean的引用的,这里暂时不涉及循环依赖,待后面的文章里再细说这里。
![](https://i-blog.csdnimg.cn/blog_migrate/ba52817c1400b69d494c0f1430b04dfd.png)
3、往下执行到AbstractAutowireCapableBeanFactory#populateBean()中,在bean级后置处理器InstantiationAwareBeanPostProcessor的具体实现AutowiredAnnotationBeanPostProcessor,会专门来处理@Autowired和@Value注解标记的依赖属性注入,具体入口方法是AutowiredAnnotationBeanPostProcessor#postProcessProperties();
![](https://i-blog.csdnimg.cn/blog_migrate/22fe04c57c59121bbbd14ff32dc599bf.png)
4、进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties()中,这和字段注入方式是同一个方法,这里就做了两件事:第一、把student对象内需要注入的teacher属性的类型、注入方式、具体执行属性注入的工具类(AutowiredFieldElement)包装成一个InjectionMetadata对象;第二,包装好的InjectionMetadata对象调用自身的inject()完成student对象依赖属性teacher的注入;注意,这个时间student的依赖属性teacher还未实例化呢
![](https://i-blog.csdnimg.cn/blog_migrate/5e41992bbd8eb9a9f15ecaed1e40dc17.png)
5、继续往里面走,进入到这个方法AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject()停一下,是个关键点;AutowiredMethodElement是AutowiredAnnotationBeanPostProcessor的内部类,和AutowiredFieldElement功能比较类似,从名字上就可判断出来,一个是用于字段注入,另一个是setter方法注入;然后在这个方法里调用 resolveMethodArguments(),注意一下,从名字就可以看出来,是用于解析方法的参数的,这里是setter方法注入,setter的参数就是Teacher,因此就可以理解这个方法就是用来解析出student对象的依赖属性teacher对象的;再往下,如果解析出来的参数对象不为空,就使用java反射调用setTeacher()方法进行依赖属性注入;
![](https://i-blog.csdnimg.cn/blog_migrate/973de442afc4cfa1238499b4049bfbc2.png)
6、setter方法依赖属性的注入到上面基本上就完成了,但是这里再进入到resolveMethodArguments()里,看看teacher又是如何被搞出来的?debug发现,resolveMethodArguments()又调用 了DefaultListableBeanFactory#resolveDependency();
![](https://i-blog.csdnimg.cn/blog_migrate/c7e62c3b3ce81195fe562225f9826564.png)
7、继续跟DefaultListableBeanFactory#resolveDependency(),发现又调用了DefaultListableBeanFactory#doResolveDependency();
![](https://i-blog.csdnimg.cn/blog_migrate/9ce4d2d29580555207f99cfcf3656b58.png)
9、好像这里也不是teacher实例化重点,继续往下,DefaultListableBeanFactory#doResolveDependency()里又调用了findAutowireCandidates(),从方法名字看是要查找注入的候选对象的,方法的返回值是student对象依赖属性teacher的Class对象,这离teacher的实例化是真不远了;
![](https://i-blog.csdnimg.cn/blog_migrate/838b395b9936f9a6134c1501c4400be6.png)
10、DefaultListableBeanFactory#doResolveDependency()里继续往下走,发现又调用了DependencyDescriptor#resolveCandidate(),从这个返回结果变量的命名上看,意思是实例化候选对象,入参数是注入属性字段的名称teacher,好像就是这了;
![](https://i-blog.csdnimg.cn/blog_migrate/afbf9c032789e7849c978cf033ec5228.png)
11、果断进入到DependencyDescriptor#resolveCandidate(),里面就干了一件事AbstractBeanFactory#getBean(java.lang.String),到这里,如果之前的Springboot扩展点系列文章都有看过,就会明白后面没啥 新鲜事了,就是把bean的实例化、属性初入、初始化等一系列流程;
![](https://i-blog.csdnimg.cn/blog_migrate/1fe578f4065b689109f7c7109772c8a5.png)
12、等teacher对象实例化、属性注入、初始化等这一系列流程走完后,就又回到了第5步,使用java反射调用Student#setTeacher()把teacher对象注入到student对象中;
![](https://i-blog.csdnimg.cn/blog_migrate/3d5a14f2a6699dc3ce0f1b8e31b39187.png)
setter注入小结
从流程上来看,字段注入方式和setter方法注入依赖的流程类似,唯一不同的就是具体执行注入操作的工具类不同;其实最应该被学习和记住的就是,spring依赖注入过程中关键方法的命名方式,真的是见名知义呀,如果业务开发里也能这样,阅读自己和别人的的代码将是一件轻松愉快的事情。