Spring依赖注入(一):字段注入的方式是如何工作的?

文章示例环境配置信息
jdk版本:1.8
开发工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE

前言

写这篇文章的起因,是因为我想写篇文章来分享一下:Spring是如何解决循环依赖的?然后在分析的时候,我发现如果要想说清楚Spring是如何解决循环依赖的,那么就必须得先说清楚什么是循环依赖?从字面理解,循环依赖肯定是发生在依赖注入的时候,那么Spring依赖注入的方式有三种注入方式,有的注入是能解决的,有的是解决不了的,那么可以解决循环依赖的注入方式的解决方法是什么?不能解决循环依赖的注入方式的原因又是什么?一连串的问题刺激到了我敏感的神经,因此,就这些疑问我打算用四篇文章分别分析它们。

什么是依赖注入

IOC(控制反转)是Spring的重要核心,即不需要手动去new一个对象,而是把对象的控制权转移给Spring容器,通常Spring容器最终创建出来的对象称为bean。在Spring容器创建bean的过程中,如果bean内属性引用了另外的bean,这里当前bean暂时称为目标bean,被引用bean简称为引用bean,那么目标bean与引用bean之间的关系就是依赖关系,即目标bean依赖引用bean,还记得UML类图中依赖关系是如何图形化表示吗?可以移步设计模式之基础:UML类图怎么看?在Spring bean生命周期中,目标bean实例化后,还不能直接使用,会接着里进行属性的注入,其中的容器内其他bean作为引用bean注入到目标bean的过程,称之为依赖注入。

依赖注入的方式

从注入的根本原理上来讲,依赖注入的方式通常是有三种分别是字段(Field)注入、setter方法注入、构造方法注入,而这三种方法最终都用到了java的反射技术,具体如下:

1、字段注入:最终调jField#set(Object obj, Object value)方法进行依赖属性的注入;

2、setter方法注入:最终调用Method#invoke(Object obj, Object... args)进行依赖属性的注入;

3、构造方法注入:最终调用jConstructor#newInstance(Object ... initargs)进行依赖属性的注入;

唯一差异比较大的就是,采用这些不同依赖注入方式的bean创建过程不同,而这正是我想分析清楚的东西。

字段注入方法

以@Autowired注解为例,即把@Autowired注解标记在目标bean的引用bean字段上;

字段注入示例

下面用一个示例,演示使用@Autowired注解,按字段注入的方式进行依赖注入,并分析注入的过程,这里想和大家分享一个经验,任何事其实都是一个方法的问题, 方法通常是解决某一个或某一类问题的,而总结后的方法是为了解决一类问题的,所以这里学会了@Autowired注解进行属性注入的工作原理的分析方法,再换成其他的注解如@Resource注解,具体内容会不一样了,但是方法是相同的。

示例内容:1、定义Teacher类; 2、定义Student类;3、在Student类依赖Teacher;4、使用@Autowired注解标记在Student类的teacher字段上,即在Student对象中以字段注入的方式注入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="小明";
    @Autowired
    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;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
        log.info("----student中的setTeacher方法被调用");
    }
}
@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;
    }
}
@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;
    }
}
@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("----单元测试执行完毕");
}

单元测试执行日志

Spring依赖注入(一):字段注入的方式是如何工作的?_第1张图片

从单元测试的执行日志来可以了解到bean从实例化到属性注入的大概过程:student对象先开始实例化,然后开始student对象依赖属性teacher的注入,但这时teacher对象还未实例化,所以又开始进行teacher对象的实例化和依赖属性注入;最后把实例化好的teacher对象注入到student对够中;

注:1、InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()执行时机是在bean实例化以后,属性注入以前;

2、BeanPostProcessor#postProcessBeforeInitialization()的执行时机是在bean属性注入完成以后;详细可以移步这里了解:Springboot扩展点之InstantiationAwareBeanPostProcessor和Springboot扩展点之BeanPostProcessor)

字段注入的工作原理

从上面的示例的单元测试结果,其实大概已经了解到Spring bean依赖注入-属性注入的工作过程,下面通过debug了解一下每个步骤的详细过程:

1、Spring中bean创建核心逻辑都在AbstractAutowireCapableBeanFactory#doCreateBean()中,有兴趣可以从Springboot启动开始一步步debug看看。那么这个方法主要干了哪些事呢?第一,bean的实例化;第二,bean的后置处理方法执行;第三,把实例化完成、未完成属性注入的bean提前暴露到Spring的第三级缓存中去;第四,bean依赖属性注入;第五,bean的初始化;student对象在完成这几步后,就可以作为Spring容器中正式的bean开始被使用了。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   // ------start-----bean实例化-------------------
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   Object bean = instanceWrapper.getWrappedInstance();
   Class beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }
    // ------end-----bean实例化-------------------
   // ------start----bean后置处理器-------------------
   synchronized (mbd.postProcessingLock) {
      if (!mbd.postProcessed) {
         try {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
         }
         catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Post-processing of merged bean definition failed", ex);
         }
         mbd.postProcessed = true;
      }
   }
    //------end----bean后置处理器-------------------
    //------start----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }
    //------end----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
   Object exposedObject = bean;
   try {
       //bean的属性注入
      populateBean(beanName, mbd, instanceWrapper);
      //bean的初始化
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      }
      else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }
   if (earlySingletonExposure) {
      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }
   // Register bean as disposable.
   try {
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

2、关于bean依赖属性的注入入口在上面AbstractAutowireCapableBeanFactory#doCreateBean()的第46行,即调用 populateBean()开始属性的注入,示例中是使用@Autowired注解,这个注解的原理就是Springboot扩展点之InstantiationAwareBeanPostProcessor,而具体实现类就是AutowiredAnnotationBeanPostProcessor;

Spring依赖注入(一):字段注入的方式是如何工作的?_第2张图片

3、进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties(),可以发现就做了两件事:第一、把student对象内需要注入的teacher属性的类型、注入方式、具体执行属性注入的工具类(AutowiredFieldElement)包装成一个InjectionMetadata对象;第二,包装好的InjectionMetadata对象调用自身的inject()完成student对象依赖属性teacher的注入;

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    //把目标bean依赖需要注入的对象类型、注入方式、执行注入的对象(AutowiredFieldElement)封装成了一个InjectionMetadata对象
   InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
   try {
       //依赖注入的入口
      metadata.inject(bean, beanName, pvs);
   }
   catch (BeanCreationException ex) {
      throw ex;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
   }
   return pvs;
}
Spring依赖注入(一):字段注入的方式是如何工作的?_第3张图片

4、进入到InjectionMetadata#inject()后,实际上调用 了AutowiredAnnotationBeanPostProcessor的内部类AutowiredFieldElement的inject(),AutowiredFieldElement#inject()内逻辑也相对比较简单:先判断一下student对象依赖的teacher对象有没有缓存,第一次创建肯定是没有的,然后就开始解析注入到student对象内的teacher字段的值,解析的过程基本上student对象的创建过程类似,先获取teacher对象,如果获取不到,就开始teacher对象的实例化、属性注入、初始化;teacher对象创建完在后,继续往下使用java反射技术对student对象内的teacher字段进行赋值;

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Field field = (Field) this.member;
   Object value;
   //判断依赖对象是否缓存
   if (this.cached) {
      try {
         value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Unexpected removal of target bean for cached argument -> re-resolve
         value = resolveFieldValue(field, bean, beanName);
      }
   }
   else {
       //实例化依赖对象的并缓存
      value = resolveFieldValue(field, bean, beanName);
   }
   if (value != null) {
       //使用java反射技术进行属性字段的赋值
      ReflectionUtils.makeAccessible(field);
      field.set(bean, value);
   }
}
Spring依赖注入(一):字段注入的方式是如何工作的?_第4张图片

5、继续往下执行,上面AbstractAutowireCapableBeanFactory#doCreateBean()的第46行,开始调用initializeBean(...)的时候,观察一下目标bean,发现student内已经完成了依赖属性teacher对象的注入;

Spring依赖注入(一):字段注入的方式是如何工作的?_第5张图片

字段注入小结

使用@Autowired注解,采用通字段注入方式,进行Spring bean依赖注入,是利用了Spring的扩展点InstantiationAwareBeanPostProcessor,具体的实现是AutowiredAnnotationBeanPostProcessor,最终执行注入操作的是其内部类AutowiredFieldElement的inject()。这里对AutowiredFieldElement要加深一下印象,AutowiredFieldElement和AutowiredMethodElement都是AutowiredAnnotationBeanPostProcessor的内部类,AutowiredFieldElement的作用通过前面的分析已经知道了,那么AutowiredMethodElement的作用是什么呢?评论区来猜一下吧,哈哈

Spring依赖注入(一):字段注入的方式是如何工作的?_第6张图片

文末彩蛋:上面分析了字段注入方式的过程,但是被@Autowired注解标记到的Teacher字段,AutowiredAnnotationBeanPostProcessor是怎么识别到的呢?

答案就在AutowiredAnnotationBeanPostProcessor的构造方法内,不光@Autowired注解,实际上@Value从配置文件中取值赋给bean,也是在这个类处理的。

public AutowiredAnnotationBeanPostProcessor() {
   this.autowiredAnnotationTypes.add(Autowired.class);
   this.autowiredAnnotationTypes.add(Value.class);
   try {
      this.autowiredAnnotationTypes.add((Class)
            ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
      logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
   }
   catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
   }
}

你可能感兴趣的:(SpringBoot工作原理,spring,java,后端,依赖注入,spring,boot)