天天看点

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

文章示例环境配置信息

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("----单元测试执行完毕");
}
           

单元测试执行日志:

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

从执行日志来看,setter方式的依赖注入和字段注入方式基本流程是一样的,student先调用无参数的构造方法进行实例化,然后在student对象内需要注入teacher对象,发现teacher对象未实例化,于是开始实例化teacher对象,待teacher对象实例化完成后(teacher对象内无引用其他bean,其属性注入、bean初始化略过),teacher对象注册到Spring容器中后,又接着student对象实例化后的后续操作,调用student的setTeacher()进行teacher对象的注入。

setter注入的工作原理

1、AbstractAutowireCapableBeanFactory#doCreateBean--->createBeanInstance(),开始student对象的实例化;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

2、AbstractAutowireCapableBeanFactory#createBeanInstance()执行完之后,student对象实例化完成,接着往下,在开始依赖属性注入之前,student对象提前暴露至Spring的第三级缓存里,这个时间很重要,也是解决bean循环依赖的关键所在,这里先加深一下印象,后面再细说。另外这里注意一下缓存的key是beanName,但是value不是一个具体的对象,而用lambda表达式写的接口ObjectFactory,ObjectFactory接口里有一个抽象方法getObject(),这里是什么意思呢?ObjectFactory#getObject()方法被触发的时候,会执行lambda表达式的内容; 而lambda表达式里的getEarlyBeanReference()正是从三级缓存里取完成实现化、未属性注入的半成品bean的引用的,这里暂时不涉及循环依赖,待后面的文章里再细说这里。

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

3、往下执行到AbstractAutowireCapableBeanFactory#populateBean()中,在bean级后置处理器InstantiationAwareBeanPostProcessor的具体实现AutowiredAnnotationBeanPostProcessor,会专门来处理@Autowired和@Value注解标记的依赖属性注入,具体入口方法是AutowiredAnnotationBeanPostProcessor#postProcessProperties();

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

4、进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties()中,这和字段注入方式是同一个方法,这里就做了两件事:第一、把student对象内需要注入的teacher属性的类型、注入方式、具体执行属性注入的工具类(AutowiredFieldElement)包装成一个InjectionMetadata对象;第二,包装好的InjectionMetadata对象调用自身的inject()完成student对象依赖属性teacher的注入;注意,这个时间student的依赖属性teacher还未实例化呢

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

5、继续往里面走,进入到这个方法AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject()停一下,是个关键点;AutowiredMethodElement是AutowiredAnnotationBeanPostProcessor的内部类,和AutowiredFieldElement功能比较类似,从名字上就可判断出来,一个是用于字段注入,另一个是setter方法注入;然后在这个方法里调用 resolveMethodArguments(),注意一下,从名字就可以看出来,是用于解析方法的参数的,这里是setter方法注入,setter的参数就是Teacher,因此就可以理解这个方法就是用来解析出student对象的依赖属性teacher对象的;再往下,如果解析出来的参数对象不为空,就使用java反射调用setTeacher()方法进行依赖属性注入;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

6、setter方法依赖属性的注入到上面基本上就完成了,但是这里再进入到resolveMethodArguments()里,看看teacher又是如何被搞出来的?debug发现,resolveMethodArguments()又调用 了DefaultListableBeanFactory#resolveDependency();

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

7、继续跟DefaultListableBeanFactory#resolveDependency(),发现又调用了DefaultListableBeanFactory#doResolveDependency();

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

9、好像这里也不是teacher实例化重点,继续往下,DefaultListableBeanFactory#doResolveDependency()里又调用了findAutowireCandidates(),从方法名字看是要查找注入的候选对象的,方法的返回值是student对象依赖属性teacher的Class对象,这离teacher的实例化是真不远了;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

10、DefaultListableBeanFactory#doResolveDependency()里继续往下走,发现又调用了DependencyDescriptor#resolveCandidate(),从这个返回结果变量的命名上看,意思是实例化候选对象,入参数是注入属性字段的名称teacher,好像就是这了;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

11、果断进入到DependencyDescriptor#resolveCandidate(),里面就干了一件事AbstractBeanFactory#getBean(java.lang.String),到这里,如果之前的Springboot扩展点系列文章都有看过,就会明白后面没啥 新鲜事了,就是把bean的实例化、属性初入、初始化等一系列流程;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

12、等teacher对象实例化、属性注入、初始化等这一系列流程走完后,就又回到了第5步,使用java反射调用Student#setTeacher()把teacher对象注入到student对象中;

Sprng依赖注入(二):setter注入是如何工作的?前言settter注入方法setter注入示例setter注入的工作原理setter注入小结

setter注入小结

从流程上来看,字段注入方式和setter方法注入依赖的流程类似,唯一不同的就是具体执行注入操作的工具类不同;其实最应该被学习和记住的就是,spring依赖注入过程中关键方法的命名方式,真的是见名知义呀,如果业务开发里也能这样,阅读自己和别人的的代码将是一件轻松愉快的事情。