Sprng依赖注入(二):setter注入是如何工作的?

布满荆棘的人生 2024-03-26 12:51 133阅读 0赞

文章示例环境配置信息
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对象;

  1. @Slf4j
  2. @Component
  3. public class Teacher {
  4. private String name = "李老师";
  5. private Student student;
  6. public Teacher() {
  7. log.info("----teacher的无参数构造方法被执行");
  8. }
  9. public Teacher(Student student) {
  10. this.student = student;
  11. log.info("----teacher的有参数构造方法被执行");
  12. }
  13. public String getName() {
  14. return name;
  15. }
  16. public void setName(String name) {
  17. this.name = name;
  18. }
  19. public Student getStudent() {
  20. return student;
  21. }
  22. public void setStudent(Student student) {
  23. log.info("----teacher中的setStudent方法被调用");
  24. this.student = student;
  25. }
  26. }
  27. @Slf4j
  28. @Component
  29. public class Student {
  30. private String name = "小明";
  31. private Teacher teacher;
  32. public Student() {
  33. log.info("----student的无参数构造方法被执行");
  34. }
  35. public Student(Teacher teacher) {
  36. this.teacher = teacher;
  37. log.info("----student的有参数构造方法(teacher)被执行");
  38. }
  39. public Student(String name, Teacher teacher) {
  40. this.name = name;
  41. this.teacher = teacher;
  42. log.info("----student的有参数构造方法(name,teacher)被执行");
  43. }
  44. public String getName() {
  45. return name;
  46. }
  47. public void setName(String name) {
  48. this.name = name;
  49. }
  50. public Teacher getTeacher() {
  51. return teacher;
  52. }
  53. @Autowired
  54. public void setTeacher(Teacher teacher) {
  55. this.teacher = teacher;
  56. log.info("----student中的setTeacher方法被调用");
  57. }
  58. }
  59. @Component
  60. @Slf4j
  61. public class MyBeanPostProcessor implements BeanPostProcessor {
  62. @Override
  63. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  64. if (beanName.equals("student")) {
  65. Student student = (Student) bean;
  66. log.info("----student属性注入完成,student.name:" + student.getName());
  67. }
  68. return bean;
  69. }
  70. }
  71. @Component
  72. @Slf4j
  73. public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
  74. @Override
  75. public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
  76. if (beanName.equals("student")||beanName.equals("teacher")) {
  77. log.info("----bean实例化完成,beanName:" + beanName);
  78. }
  79. return true;
  80. }
  81. }
  82. @Test
  83. public void test4() {
  84. log.info("----单元测试执行开始");
  85. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
  86. Student bean = context.getBean(Student.class);
  87. log.info("----bean:"+bean.getName());
  88. log.info("----单元测试执行完毕");
  89. }

单元测试执行日志:

a6be5410d7ab4915bc0762ccb5580672.png

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

setter注入的工作原理

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

fcd2348404dd4ad18e68962d9784c679.png

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

3cae16e24f8d42c885d38331bc206f3e.png

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

b31434552dbd4de7913e75059ddef98e.png

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

5a6cba9261234f54821523d96877133e.png

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

cbda242c75ec4b58bbdcabbf3ebd1e2d.png

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

78c4a4fa09d24d648250f95db2f75ee3.png

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

51c4ac56b85948aca3de01cbf4379c2d.png

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

172e6956267e4fa4b99aad761a88c75a.png

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

ae0f2366b0464b4f83d3a126b52e7db5.png

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

40f1defc7a39475d8781fec9e588f2eb.png

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

e413c34c089348faa2874773e07a6762.png

setter注入小结

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

发表评论

表情:
评论列表 (有 0 条评论,133人围观)

还没有评论,来说两句吧...

相关阅读