Spring依赖注入@Autowired深层原理、源码级分析,感受DI带来的编程之美【享学Spring】(中)

简介: Spring依赖注入@Autowired深层原理、源码级分析,感受DI带来的编程之美【享学Spring】(中)

AutowiredFieldElement实现属性依赖注入


这个类继承自静态抽象内部类InjectionMetadata.InjectedElement,并且它还是AutowiredAnnotationBeanPostProcessor的private内部类,体现出非常高的内聚性:


  // 它的宿主类是AutowiredAnnotationBeanPostProcessor 高内聚低耦合
  private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
    private final boolean required;
    private volatile boolean cached = false;
    @Nullable
    private volatile Object cachedFieldValue;
    //唯一构造方法
    public AutowiredFieldElement(Field field, boolean required) {
      super(field, null); // 此处显示调用父类的构造函数
      this.required = required;
    }
    // 核心方法:字段的依赖注入
    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
      // 显然此处父类的member就指的是filed
      Field field = (Field) this.member;
      Object value;
      // 走缓存,关于cachedFieldValue的值  且听下文分解
      if (this.cached) {
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      } else {
        // 每个Field都包装成一个DependencyDescriptor
        // 如果是Method包装成DependencyDescriptor,毕竟一个方法可以有多个入参
        // 此处包装成它后,显然和元数据都无关了,只和Field有关了  完全隔离
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        // 转换器使用的bean工厂的转换器~~~
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
          // 获取依赖的value值的工作  最终还是委托给beanFactory.resolveDependency()去完成的~~~~
          // 这个接口方法由AutowireCapableBeanFactory提供,它提供了从bean工厂里获取依赖值的能力~
          value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        } catch (BeansException ex) {
          throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        // 下面代码是把缓存值缓存起来  让同一个Field注入多次能提高效率
        synchronized (this) {
          if (!this.cached) {
            // 可以看到value!=null并且required=true才会进行缓存的处理
            // 按照上例 此处value值为A@2277实例
            if (value != null || this.required) {
              this.cachedFieldValue = desc; // 可以看到缓存的值是上面的DependencyDescriptor对象~~~~
              // 这个方法是宿主类AutowiredAnnotationBeanPostProcessor的方法
              // 简单的说就是注册到bean工厂去,比如此处b是依赖a的  所以就注册这个依赖关系进去了
              // 参考this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
              registerDependentBeans(beanName, autowiredBeanNames);
              // autowiredBeanNames里可能会有别名的名称~~~所以size可能大于1
              if (autowiredBeanNames.size() == 1) {
                // beanFactory.isTypeMatch挺重要的~~~~因为@Autowired是按照类型注入的
                String autowiredBeanName = autowiredBeanNames.iterator().next();
                if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                  this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());
                }
              }
            } else {
              this.cachedFieldValue = null;
            }
            this.cached = true;
          }
        }
      }
      // 不为null,就完成最终的set值  利用反射给filed属性赋值~~~~
      if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
      }
    }
  }


前面准备好的注解的、需要注入的元数据信息,这一步使用执行注入。

从源码中也能发现一个特点,Spring大量的使用到了转换、适配、委托等机制。因此最终最最最重要的一行代码无非是这一句:


value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);


委托AutowireCapableBeanFactory来获取到依赖值value,从而最终完成注入(反射执行注入~)。

so,其实最核心还是在Bean工厂里,也就是它的唯一内建实现类DefaultListableBeanFactory

DefaultListableBeanFactory解析依赖值


分离关注,本处只看它解析依赖值的方法resolveDependency():

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
  ...
  @Override
  @Nullable
  public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    //此处使用的是DefaultParameterNameDiscoverer 来获取参数名
    // 这就是为什么我们使用@Autowired注入,即使有多个同类型的Bean,也可以通过field属性名进行区分的根本原因(可以不需要使用@Qualifier注解)
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 支持Optional  原来还是doResolveDependency  暂略
    if (Optional.class == descriptor.getDependencyType()) {
      return createOptionalDependency(descriptor, requestingBeanName);
    } 
    // 原理也是doResolveDependency  
    else if (ObjectFactory.class == descriptor.getDependencyType() ||
        ObjectProvider.class == descriptor.getDependencyType()) {
      return new DependencyObjectProvider(descriptor, requestingBeanName);
    } else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
      return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    } else { // 绝大部分情况下  肯定走到这里~~~~
      // 此处特别特别的需要重视:这是@Lazy支持的根本原因~~
      // 关于AutowireCandidateResolver我建议小伙伴先看下一个章节~~~
      // 此处若通过AutowireCandidateResolver解析到了值就直接返回了(若标注了@Lazy,此处的result将不会为null了~~~)
      Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
      // doResolveDependency是绝大多数情况下  最终会去执行的代码(若result仍旧为null的话)
      if (result == null) {
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
      }
      return result;
    }
  }
  ...
}


截止到这,整个流程中有两句非常重要的代码不容忽视:

第一句是:


/

Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName)

它会解析@Lazy这个注解,来创建代理对象。而如果它已经创建了,就不会执行第二句了。

关于这句代码的详细解释、分析,请参阅(强烈建议阅读一下本文):

【小家Spring】Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理


第二句:(重中之重)


绝大多数情况下,肯定会经由我们的doResolveDependency()方法来处理,因此这个方法的实现才是重中之重。


看过上文你就能知道:其实getLazyResolutionProxyIfNecessary()它的底层依赖还是doResolveDependency()这个方法


看看DefaultListableBeanFactory对这个"重中之重"的实现:

// 此处autowiredBeanNames是在inject的一个空的Set
// autowired表示最终可以注入进去的bean名称们(因为可能是会有多个符合条件的)
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
  ...
  @Nullable
  public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // 记录注入点,其实使用的ThreadLocal~
    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    try {
      // 只有ShortcutDependencyDescriptor实现了resolveShortcut方法,其实就是根据
      // 实现也就一句话:beanFactory.getBean(this.shortcut, this.requiredType)
      // ShortcutDependencyDescriptor实在inject完成后创建的,就是有缓存的效果~~~
      Object shortcut = descriptor.resolveShortcut(this);
      if (shortcut != null) {
        return shortcut;
      }
      // interface com.fsx.dependency.AInterface
      Class<?> type = descriptor.getDependencyType();
      // 拿到@Value注解的value值(是个字符串)  若没有标注@Value  显然就不用那啥了
      // 从此处其实也可以看出,@Value注解的优先级对于找到bean来说还是蛮高的
      Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
      // ============ 这部分主要是解析@Value注解
      // 解析它的占位符,解析它的SpEL表达式
      // 相关处理类曹靠BeanExpressionResolver和StandardBeanExpressionResolver  StandardEvaluationContext等
      // 因为关于@Value的文章里详细解过,此处一笔带过~~~
      if (value != null) {
        if (value instanceof String) {
          String strVal = resolveEmbeddedValue((String) value);
          BeanDefinition bd = (beanName != null && containsBean(beanName) ?
              getMergedBeanDefinition(beanName) : null);
          value = evaluateBeanDefinitionString(strVal, bd);
        }
        TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
        try {
          return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
        } catch (UnsupportedOperationException ex) {
          // A custom TypeConverter which does not support TypeDescriptor resolution...
          return (descriptor.getField() != null ?
              converter.convertIfNecessary(value, type, descriptor.getField()) :
              converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
        }
      }
      // ============
      // 此处处理的是多值注入的情况,比如注入Stream<Object>、Map、Array、Collection等等  Spring都是支持的
      // 需要稍微注意一点,Stream这种类型的注入在Spring4以上才得到支持的
      Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
      if (multipleBeans != null) {
        return multipleBeans;
      }
      // 显然绝大部分情况下,都会走到这里(因为大部分我们都是单值注入~)
      // findAutowireCandidates可以说又是整个依赖注入的灵魂之一~~~~ 它的查找步骤简述如下:
      //1、BeanFactoryUtils.beanNamesForTypeIncludingAncestors() 找到这种类型的所有的beanNames(candidateNames)(可能有多个哟,但大多数情况下只有一个)
      //2、处理resolvableDependencies比如ApplicationContext的依赖,让他们也能够正常注入进去(他们可不作为bean存在容器里~)
      //3、遍历candidateNames:检查它是否可以被依赖、容器内是否存在bean定义(或者Singleton) 若符合,getBean()出来放在map里
      //4、若返回的Map不为Empty()了,直接return  表示找到了(当然还可能是多个~~)
      // 若返回的还是Empty,那就继续检查requiredType是否为Map、Collection等类型,从它里面去找。。。(这种注入case使用太少,此处暂略~)
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      // 所以matchingBeans证明没有找到匹配的,但requied=true,所以此处就抛出异常了~
      if (matchingBeans.isEmpty()) {
        if (isRequired(descriptor)) {
          raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
        }
        return null;
      }
      String autowiredBeanName;
      Object instanceCandidate;
      // 若有多个匹配
      // 需要注意的是:@Qualifier注解在上面就已经生效了~~~~因为AutowireCandidateResolver.isAutowireCandidate是在上面生效的
      if (matchingBeans.size() > 1) {
        // 由它进行判别  从弱水三千中  取出一瓢
        // 1、是否标注有@Primary  有这种bean就直接返回(@Primary只允许标注在一个同类型Bean上)
        // 2、看是否有标注有`javax.annotation.Priority`这个注解的
        // 3、根据字段field名,去和beanName匹配  匹配上了也行(这就是为何我们有时候不用@Qulifier也没事的原因之一)
        // 此处注意:descriptor.getDependencyName()这个属性表示字段名,靠的是`DefaultParameterNameDiscoverer`去把字段名取出来的~
        autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
        if (autowiredBeanName == null) {
          if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
            // 此处抛出异常:NoUniqueBeanDefinitionException
            return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
          } else {
            // In case of an optional Collection/Map, silently ignore a non-unique case:
            // possibly it was meant to be an empty collection of multiple regular beans
            // (before 4.3 in particular when we didn't even look for collection beans).
            return null;
          }
        }
        instanceCandidate = matchingBeans.get(autowiredBeanName);
      } else { // 只有一个匹配就啥都说了  干就完了
        // We have exactly one match.
        Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
        autowiredBeanName = entry.getKey();
        instanceCandidate = entry.getValue();
      }
      if (autowiredBeanNames != null) {
        autowiredBeanNames.add(autowiredBeanName);
      }
      if (instanceCandidate instanceof Class) {
        // 此处getBean() 拿到该类型的实例对象了~~~
        instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
      }
      Object result = instanceCandidate;
      if (result instanceof NullBean) {
        if (isRequired(descriptor)) {
          raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
        }
        result = null;
      }
      if (!ClassUtils.isAssignableValue(type, result)) {
        throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
      }
      return result;
    } finally {
      ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
    }
  }
  ...
}
相关文章
|
7天前
|
小程序 数据可视化 Java
Java+后端Spring boot 开发的全套UWB定位方案,0.1米高精度定位系统源码
UWB定位系统由硬件定位设备、定位引擎和应用软件组成。该定位系统应用软件支持PC端和移动端访问,并提供位置实时显示、历史轨迹回放、人员考勤、电子围栏、行为分析、智能巡检等功能。定位精度高达10cm,同时具备高动态、高容量、低功耗的优点。应用场景包括:隧道、化工、工厂、煤矿、工地、电厂、养老、展馆、整车、机房、机场等。
32 8
|
20天前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
35 3
|
1天前
|
Java Maven 数据安全/隐私保护
详解 Java AOP:面向方面编程的核心概念与 Spring 实现
详解 Java AOP:面向方面编程的核心概念与 Spring 实现
10 1
|
1天前
|
XML Java API
IoC 之 Spring 统一资源加载策略【Spring源码】
IoC 之 Spring 统一资源加载策略【Spring源码】
11 2
|
3天前
|
监控 Java API
【监控】spring actuator源码速读
【监控】spring actuator源码速读
7 1
|
3天前
|
监控 Java 关系型数据库
java版MES系统源码,后端采用 Spring Boot 多模块架构
MES系统采用Vue3的vue-element-plus-admin为后台,Spring Boot多模块架构,支持MySQL、Oracle等数据库,具备SaaS多租户功能。核心功能包括车间计划排程、工艺流程配置、生产质量管理、进度追踪、库存和排班管理等,全面覆盖生产运营关键环节。
java版MES系统源码,后端采用 Spring Boot 多模块架构
|
4天前
|
Java Spring
Spring源码学习——(二)
第二讲——了解BeanFactory的功能
|
4天前
|
Java Spring 容器
Spring源码学习——(一)
第一讲——了解BeanFactory和ApplicationContext
|
12天前
|
Java Spring
【JavaEE进阶】 Spring AOP源码简单剖析
【JavaEE进阶】 Spring AOP源码简单剖析
|
18天前
|
Java 数据库 Spring
Spring 事务 (编程式 & 声明式, Spring 事务传播机制)
Spring 事务 (编程式 & 声明式, Spring 事务传播机制)
24 1