spring 源码解析之populateBean属性注入

前言

通过上一篇spring 源码解析之 createBeanInstance已经了解了 spring 如何创建 bean 实例的,创建完实例之后,接下来就是属性注入的问题了。本篇文章就是解释 spring 如何进行属性注入的。开卷!

源码解析

populateBean概述

  • populateBean方法主要负责对@Autowired、@Resource、@Value等注解标注的属性进行填充,一些普通的成员变量则通过反射或者构造函数、set 方法等注入
  • 根据bean定义从给定的BeanWrapper中获取属性值,并在设置属性之前允许任何InstantiationAwareBeanPostProcessors修改bean的状态(这个是后置处理器内容,后续详细讲)。
  • 根据bean定义的配置,可以进行自动装配类型(ByName或ByType)的属性值设置,并且可以进行属性值的后处理。
  • 最后,将属性值应用于bean实例中。直接看源码。
java 复制代码
 protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    if (bw == null) {
       // 如果beanWrapper为空,说明bean实例为null,需要跳过property值的设置
       // 但是如果beanWrapper为空而且mbd.hasPropertyValues()为true,说明bean实例为null,bean定义中存在property值,需要抛出异常
       if (mbd.hasPropertyValues()) {
          throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
       }
       else {
          // Skip property population phase for null instance.
          return;
       }
    }
 ​
    // 如果Bean是记录类型,那么Spring将跳过属性填充阶段,因为记录类型的属性是不可变的,无法进行属性注入
    if (bw.getWrappedClass().isRecord()) {
       if (mbd.hasPropertyValues()) {
          throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");
       }
       else {
          // Skip property population phase for records since they are immutable.
          return;
       }
    }
 ​
    // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
    // state of the bean before properties are set. This can be used, for example,
    // to support styles of field injection.
    // 在设置属性之前,让任何InstantiationAwareBeanPostProcessors都有机会修改bean的状态。例如,这可以用于支持现场注入的样式。
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
       for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
          if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
             return;
          }
       }
    }
 ​
    // 属性填充值
    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
 ​
    // 自动装配类型 byName byType
    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
       // 根据pvs 封装属性值
       MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
       // Add property values based on autowire by name if applicable.
       if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
          // 添加属性值,根据byName
          autowireByName(beanName, mbd, bw, newPvs);
       }
       // Add property values based on autowire by type if applicable.
       if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
          // 添加属性值,根据byType
          autowireByType(beanName, mbd, bw, newPvs);
       }
       pvs = newPvs;
    }
    // 此工厂是否持有InstantiationAwareBeanPostProcessor后置处理器
    if (hasInstantiationAwareBeanPostProcessors()) {
       if (pvs == null) {
          pvs = mbd.getPropertyValues();
       }
       // 遍历后置处理器,如果返回非空的PropertyValues,将pvs更新为返回的PropertyValues
       for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
          PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
          if (pvsToUse == null) {
             return;
          }
          pvs = pvsToUse;
       }
    }
 ​
    boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
    if (needsDepCheck) {
       PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
       checkDependencies(beanName, mbd, filteredPds, pvs);
    }
 ​
    if (pvs != null) {
       // 将属性应用到 bean 中
       applyPropertyValues(beanName, mbd, bw, pvs);
    }
 }
  1. 检查传入的BeanWrapper对象是否为空,如果为空且bean定义中有属性值,则抛出异常,表示不能对null实例应用属性值。若两者都为空,则直接跳过属性填充阶段。

  2. 如果bean是Java记录类型(Record),由于其属性不可变(有点类似于 final),因此也跳过属性填充阶段。若记录类型存在属性值,则同样抛出异常。

  3. 在设置属性之前,调用注册在容器中的所有InstantiationAwareBeanPostProcessor,允许它们有机会通过postProcessAfterInstantiation方法修改bean实例的状态,这也相当于 AOP 中的后置处理器进行方法增强。

  4. 根据bean定义中的自动装配模式(resolvedAutowireMode)决定如何填充属性值:

    • AUTOWIRE_BY_NAME:根据bean名称自动注入属性。
    • AUTOWIRE_BY_TYPE:根据类型自动注入属性。
  5. 再次遍历InstantiationAwareBeanPostProcessor,让它们有机会通过postProcessProperties方法进一步修改或替换已填充的属性值。

  6. 如果bean定义启用了依赖检查功能,那么会进行依赖项检查以确保注入的属性值满足依赖关系。

  7. 最后,使用最终确定的属性值集合(PropertyValues)调用applyPropertyValues方法,将这些属性值实际地应用到bean实例上

  8. 总结来说,这个函数实现了Spring框架在创建bean实例后的初始化阶段,处理自动装配属性值注入等核心功能,并允许自定义扩展点对这一过程进行干预和定制。

举个例子解释一下上面涉及到几个参数的意思

java 复制代码
public class Person {
    private String name;
    
    @Autowired
    private Address address;

    // getters and setters...
}

public class Address {
    private String street;
    private String city;

    // getters and setters...
}

@Configuration
public class AppConfig {

    @Bean
    public Address address() {
        Address address = new Address();
        address.setStreet("123 Main St");
        address.setCity("Springfield");
        return address;
    }

    @Bean
    public Person person(Address address) {
        Person person = new Person();
        person.setName("John Doe");
        // 由于使用了@Autowired注解,此处可以不手动设置address
        return person;
    }
}

解释上面的代码

  • 在Spring容器初始化Person bean时,会调用populateBean进行属性注入。

  • mbd(RootBeanDefinition)包含了对Person bean的所有定义信息,包括属性值和自动装配模式。

  • bw(BeanWrapper)封装了Person对象实例。

  • 在populateBean方法执行过程中

    1. 首先检查bw是否为空,如果不为空则继续处理。
    2. 由于配置中没有指定Person的address属性值,但设置了自动装配模式(默认或显式设置为AUTOWIRE_BY_TYPE),所以会根据类型找到并注入匹配的Address bean到Person的address属性上。
    3. 如果存在任何InstantiationAwareBeanPostProcessor,则会在注入属性前和后分别调用它们的方法进行处理。
    4. 最终,将所有确定好的属性值(包括直接赋值的name和通过自动装配获取的address)封装在PropertyValues(pvs)中。
    5. 调用applyPropertyValues方法,将pvs中的属性值应用到Person实例中,即设置其name属性为"John Doe",并将从address bean中获取的地址信息注入到Person的address属性中。

populateBean 详细解析

autowireByName

此函数实现了按名称自动装配策略,即如果一个Bean的属性名与另一个已存在的Bean名相匹配,则会将这个Bean实例添加到MutablePropertyValues待注入的属性值

java 复制代码
/**
 * Fill in any missing property values with references to
 * other beans in this factory if autowire is set to "byName".
 * @param beanName the name of the bean we're wiring up.
 * Useful for debugging messages; not used functionally.
 * @param mbd 存着 bean 定义的信息
 * @param bw 存着 JavaBean 类信息
 * @param pvs 需要填充的属性值
 */
protected void autowireByName(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

   // 找出当前Bean中需要依赖注入且尚未填充的属性名称数组propertyNames
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   for (String propertyName : propertyNames) {
      // 检查缓存是否有当前 bean
      // 如果到了依赖注入的时候,此时所依赖的 bean 肯定都已经实例化完成了
      if (containsBean(propertyName)) {
         // 递归获取 bean 实例
         Object bean = getBean(propertyName);
         // 添加到传入的MutablePropertyValues参数pvs中
         pvs.add(propertyName, bean);
         // 记录依赖关系,将当前Bean(beanName)注册为依赖于新注入的Bean(propertyName)的Bean
         registerDependentBean(propertyName, beanName);
         if (logger.isTraceEnabled()) {
            logger.trace("Added autowiring by name from bean name '" + beanName +
                  "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
                  "' by name: no matching bean found");
         }
      }
   }
}
  • 函数首先调用unsatisfiedNonSimpleProperties(mbd, bw)来找出当前Bean中需要依赖注入且尚未填充的属性名称数组propertyNames。

  • 针对数组中的每个属性名称:

    1. 使用containsBean(propertyName)检查当前BeanFactory中是否存在与该属性名称相同的Bean。
    2. 如果存在,则通过getBean(propertyName)获取对应的Bean实例,并将其添加到传入的MutablePropertyValues参数pvs中,即将该Bean实例注入到原Bean的对应属性上
    3. 调用registerDependentBean(propertyName, beanName)记录依赖关系,将当前Bean(beanName)注册为依赖于新注入的Bean(propertyName)的Bean。
    4. 若在BeanFactory中找不到与属性名称匹配的Bean,则同样根据日志级别输出未找到匹配Bean的信息,此时不进行自动装配。

autowireByType

autowireByName类型,不同的是此方法按类型自动注入(autowire by type)的行为。它会根据Bean的类型在Bean工厂中查找依赖的Bean,并将其注入到需要依赖注入的属性中。

java 复制代码
/**
 * Abstract method defining "autowire by type" (bean properties by type) behavior.
 * <p>This is like PicoContainer default, in which there must be exactly one bean
 * of the property type in the bean factory. This makes bean factories simple to
 * configure for small namespaces, but doesn't work as well as standard Spring
 * behavior for bigger applications.
 * @param beanName 待注入依赖项的目标Bean的名称
 * @param mbd 合并后的Bean定义,用于更新通过自动注入的信息
 * @param bw 一个BeanWrapper对象,它封装了目标Bean,提供了获取Bean属性信息的能力
 * @param pvs MutablePropertyValues对象,用于存储注册的已连接对象(即被注入的Bean)
 */
protected void autowireByType(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

   // 获取类型转换器
   TypeConverter converter = getCustomTypeConverter();
   // bw是BeanWrapper类型的对象,它内部已经包含了基本类型和常见对象类型的转换逻辑,可以处理大部分类型转换的需求
   if (converter == null) {
      converter = bw;
   }

   // 获取需要依赖注入的属性名
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   Set<String> autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2);
   for (String propertyName : propertyNames) {
      try {
         // 获取属性描述符 以获得其类型和访问方法(getter/setter)
         PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
         // Don't try autowiring by type for type Object: never makes sense,
         // even if it technically is an unsatisfied, non-simple property.
         // 检查属性类型是否为Object类,由于按类型注入Object没有实际意义,所以跳过这类属性
         if (Object.class != pd.getPropertyType()) {
            // 获取set方法参数
            MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
            // Do not allow eager init for type matching in case of a prioritized post-processor.
            boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
            // 创建一个DependencyDescriptor来表示当前属性的依赖关系,其中包含了相关的方法参数信息以及是否允许优先初始化的标志
            DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
            // 解析指定beanName 的属性所匹配的值,并把解析到的属性名存储在 autowiredBeanNames  中
            // 当属性存在多个封装bean时,如 @Autowired List<Bean> beans,会找到所有的匹配Bean 类型的bean并将其注入。
            // 这里的返回值是真正的需要注入的属性, autowiredBeanNames 是需要注入的属性(可能是集合)的names
            Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
            if (autowiredArgument != null) {
               // 添加到待注入的bean列表中
               pvs.add(propertyName, autowiredArgument);
            }
            for (String autowiredBeanName : autowiredBeanNames) {
               // 注册依赖关系。操作 dependentBeanMap 和  dependenciesForBeanMap 集合
               registerDependentBean(autowiredBeanName, beanName);
               if (logger.isTraceEnabled()) {
                  logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" +
                        propertyName + "' to bean named '" + autowiredBeanName + "'");
               }
            }
            autowiredBeanNames.clear();
         }
      }
      catch (BeansException ex) {
         throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
      }
   }
}
  • 获取自定义的类型转换器,如果不存在,则使用传入的BeanWrapper作为默认的类型转换器。

    这里可以使用 bw 作为转换器的原因是因为BeanWrapper类型的对象它内部已经包含了基本类型和常见对象类型的转换逻辑,可以处理大部分类型转换的需求

  • unsatisfiedNonSimpleProperties确定目标Bean中未满足且非简单的属性名,这些属性需要进行自动注入。下面会详细讲

  • 遍历每个未满足的属性:

    1. 获取当前属性的描述符(PropertyDescriptor)以获得其类型和访问方法(getter/setter)
    2. 检查属性类型是否为Object类,由于按类型注入Object没有实际意义,所以跳过这类属性。
    3. 创建一个DependencyDescriptor来表示当前属性的依赖关系,其中包含了相关的方法参数信息以及是否允许优先初始化的标志。
    4. 使用resolveDependency方法查找与当前属性类型相匹配的Bean实例,并将匹配到的Bean添加到autowiredBeanNames集合中。
    5. 如果找到了匹配的Bean实例,则将其注入到目标Bean的相应属性上,并将注入信息添加到pvs中。
    6. 将所有注入的Bean名称记录在依赖关系映射中,以便后续进行生命周期管理等操作。
    7. 清空autowiredBeanNames集合,以便处理下一个属性。
    8. 在解析和注入过程中,如果遇到异常(如找不到匹配的依赖Bean),则抛出UnsatisfiedDependencyException,提供详细的错误信息。

unsatisfiedNonSimpleProperties

从一个已创建的bean中找出所有未被设置且类型非简单的属性(即它们不是基本类型或字符串类型,可能是其他bean的引用

java 复制代码
protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
   Set<String> result = new TreeSet<>();
   // 获取bean 的property 属性
   PropertyValues pvs = mbd.getPropertyValues();
   // 获取 bw 中的属性描述
   PropertyDescriptor[] pds = bw.getPropertyDescriptors();
   for (PropertyDescriptor pd : pds) {
      // 存在 set 方法 && 属性不被排除 && 不存在在属性值中 && 不是简单类型
      if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
            !BeanUtils.isSimpleProperty(pd.getPropertyType())) {
         // 添加到结果集中
         result.add(pd.getName());
      }
   }
   return StringUtils.toStringArray(result);
}
  • 初始化一个TreeSet类型的集合result,用于存储满足条件的非简单属性名称。

  • mbd获取该bean的所有属性值(PropertyValues pvs)。

  • 通过bw获取bean的所有属性描述符(PropertyDescriptor[] pds)。

  • 遍历每一个属性描述符pd:

    • 检查pd是否有写入方法(即setter方法),这意味着属性是可以被外部设置的;
    • 确定该属性是否被排除在依赖检查之外;
    • 检查属性值集合pvs中是否包含该属性名称,若不包含则表示该属性尚未被设置,因为如果包含则是手动注入,不是自动装配了
    • 使用BeanUtils.isSimpleProperty(pd.getPropertyType())判断该属性类型是否为简单类型,如int, String等,如果不是,则认为是复杂类型,可能是指向其他bean的引用;
    • 若以上条件都满足,则将该属性名称添加到结果集result中。
    • 最后,使用StringUtils.toStringArray(result)将结果集合转换成字符串数组并返回。

resolveDependency

下面继续看DefaultListableBeanFactory#resolveDependency,用于解决依赖注入中依赖对象的解析。根据依赖对象的类型进行不同处理,包括处理可选依赖、依赖工厂、依赖提供者和一般类型依赖。如果依赖对象是懒加载的,会创建一个代理对象注入bean。最后返回解析后的依赖对象。

java 复制代码
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

   descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
   // 针对不同类型的不同处理
   if (Optional.class == descriptor.getDependencyType()) {
      return createOptionalDependency(descriptor, requestingBeanName);
   }
   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 {
      // 处理bean是否懒加载,如果懒加载,创建一个代理对象注入bean
      Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
      if (result == null) {
         // 针对一般类型的通用
         result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
      }
      return result;
   }
}

这里我们主要关注最后面的,一般通用类型的依赖解析doResolveDependency

doResolveDependency

它的主要任务是根据给定的DependencyDescriptor参数解析并解决依赖关系,最终返回符合要求的对象实例。

java 复制代码
/**
 * 确实解决依赖关系
 *
 * @param descriptor         这个参数封装了依赖注入点的详细信息,可以是字段(Field),也可以是方法参数(MethodParameter)。它包含了一些关于依赖的信息,如依赖是否必须存在,依赖的类型等
 * @param beanName           bean名称
 * @param autowiredBeanNames 这个参数是一个集合,用于收集所有自动装配的bean的名称。当Spring解析依赖并找到匹配的bean时,会将这些bean的名称添加到这个集合中
 * @param typeConverter      型转换器
 */
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

   // 设置当前注入点
   InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
   try {
      // 尝试从缓存中获取
      Object shortcut = descriptor.resolveShortcut(this);
      if (shortcut != null) {
         return shortcut;
      }

      // 获取依赖的类型
      Class<?> type = descriptor.getDependencyType();
      // 取值@Value注解中的value属性中的值,这里取出的值是未经修改的值,即带有 ${} 标签的值。如果descriptor未被@Value标注,则返回null
      Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
      if (value != null) {
         // 到这里说明属性被 @Value 注解修饰了,这里是解析 @Value 注解的逻辑
         // 如果value不为null,
         if (value instanceof String strValue) {
            // 处理占位符如${},做占位符的替换(不解析SP EL表达式)
            String resolvedValue = resolveEmbeddedValue(strValue);
            BeanDefinition bd = (beanName != null && containsBean(beanName) ?
                  getMergedBeanDefinition(beanName) : null);
            //    解析SP EL(如#{})
            value = evaluateBeanDefinitionString(resolvedValue, bd);
         }
         TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
         try {
            // 类型转换,把解析出来的结果转成type类型
            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()));
         }
      }

      // 对集合类型进行处理,包括,Array、Collection、Map。后面详解
      Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
      if (multipleBeans != null) {
         return multipleBeans;
      }

      // 调用查找所有类型为type的实例,存放在matchingBeans <beanName, bean> (在 resolveMultipleBeans 方法中也是核心也是调用该方法)。下面详解
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      if (matchingBeans.isEmpty()) {
         if (isRequired(descriptor)) {
            // 如果没有找到,并且bean 并标注为 required = true, 则抛出NoSuchBeanDefinitionException异常
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         return null;
      }

      String autowiredBeanName;
      Object instanceCandidate;

      // 如果找到了不止一个匹配的bean,Spring 按照一定规则进行挑选
      if (matchingBeans.size() > 1) {
         // 按以下顺序,找到符合条件的就直接返回
         // 1. 挑选出被标识为primary的bean
         // 2. 挑选标识了@Priority,且先级级最高的bean。可以不标识,一旦标识,不允许同一优先级的存在
         // 3. fallback,依赖的名称与matchingBeans中任意一Key匹配
         autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
         if (autowiredBeanName == null) {
            if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
               // 非集合类,找到了多个符合条件的Bean,抛出异常
               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) {
         // 将待装配的Bean名称放入autowiredBeanNames集合里
         autowiredBeanNames.add(autowiredBeanName);
      }
      if (instanceCandidate instanceof Class) {
         // 这里又去调用 getBean 方法去获取bean
         instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
      }
      Object result = instanceCandidate;
      if (result instanceof NullBean) {
         if (isRequired(descriptor)) {
            // 如果 result 是 NullBean类型,且 required = true,则抛出异常
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         result = null;
      }
      // 类型校验,确保类型与解析出来的Bean实例能够匹配
      if (!ClassUtils.isAssignableValue(type, result)) {
         throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
      }
      return result;
   }
   finally {
      ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
   }
}
  1. 设置当前注入点:使用ConstructorResolver.setCurrentInjectionPoint()记录当前正在处理的注入点信息。稍微解释一下

    • 再简单来说,比如有 10 个依赖需要注入,这次注入第一个,那就记录我现在注入的是第一个相关的信息,然后第一个注入完之后,注入第二个的时候就不用判断第一个是否已经注入了,因为设置了切入点,直接从切入点往下继续注入第二个
  2. 尝试从缓存获取快捷方式:检查是否有之前已经计算过的快捷引用,如果存在则直接返回。

  3. 获取依赖类型,并尝试解析@Value注解:如果属性被@Value注解修饰,则提取注解值,处理其中的占位符(如${...}),然后进行SpEL表达式(如#{...})的解析及类型转换,确保结果与依赖类型的匹配性。 这里稍微解释一下这三个方法。

    • resolveEmbeddedValue:这个方法主要是来读取如@Value("${my.property}")这种注解的占位符,将配置文件的值读取出来进行替换

    • getMergedBeanDefinition:获取合并后的Bean定义对象(BeanDefinition),即包含了父级Bean定义以及其他任何影响该Bean的元数据信息。

    • evaluateBeanDefinitionString:用于在给定的Bean定义上下文中评估一个字符串表达式,这通常涉及到对SpEL表达式的解析与执行。例如

      xml 复制代码
         <bean id="exampleBean" class="com.example.ExampleBean">
           <property name="message" value="#{systemProperties['user.home']}/config/message.txt"/>
         </bean>
      • 在这里,#{systemProperties['user.home']}是一个SpEL表达式,它会在运行时动态计算得到系统的用户主目录。Spring容器在处理message属性时,会调用evaluateBeanDefinitionString方法来计算这个SpEL表达式,并将结果作为实际值注入到ExampleBean的message字段中
  4. 处理集合类型(Array、Collection、Map)的依赖注入:调用resolveMultipleBeans()方法来查找并注入多个符合条件的bean。

  5. 查找所有候选的bean:通过findAutowireCandidates()方法查找类型匹配的所有bean实例。

  6. 选择合适的bean:在找到多个候选bean时,按照优先级规则(primary bean、@Priority注解排序等)确定唯一的一个autowiredBeanName及其对应的bean实例

  7. 如果是可选依赖且未找到匹配项或者找到多个但不符合单例条件,则根据具体情况决定是否抛出异常或返回null。

  8. 类型检查:确保选定的bean实例可以被安全地赋值给原始依赖类型。

  9. 更新自动装配的bean名称集合并返回结果:将实际装配的bean名称添加到autowiredBeanNames集合中,并返回已解决的依赖对象实例。

  10. 最后,在函数结束时恢复先前的注入点信息。

    总之,这个函数实现了Spring框架核心的依赖注入逻辑,涵盖了单例、多例、注解驱动的值注入以及集合类型的依赖解析等多种场景。

resolveMultipleBeans

resolveMultipleBeans 主要用于处理Spring框架中的自动装配逻辑,针对不同的依赖类型(如Stream、数组、集合或Map),它会查找符合条件的bean并进行相应的转换和排序,最终返回一个注入结果

java 复制代码
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {

   // 获取依赖的类型
   Class<?> type = descriptor.getDependencyType();

   // 如果是 StreamDependencyDescriptor 类型,则返回流的形式
   // 我在开发中未遇到过有这种类型的 bean
   if (descriptor instanceof StreamDependencyDescriptor streamDependencyDescriptor) {
      // 寻找并返回能够自动装配给定依赖类型的候选bean集合
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      if (autowiredBeanNames != null) {
         autowiredBeanNames.addAll(matchingBeans.keySet());
      }
      // 将这些bean根据名称解析为对象,并过滤掉值为 NullBean 的bean
      Stream<Object> stream = matchingBeans.keySet().stream()
            .map(name -> descriptor.resolveCandidate(name, type, this))
            .filter(bean -> !(bean instanceof NullBean));
      // 如果依赖需要排序,则按照自定义比较器对流进行排序,最后返回这个流对象
      if (streamDependencyDescriptor.isOrdered()) {
         stream = stream.sorted(adaptOrderComparator(matchingBeans));
      }
      return stream;
   }
   // 数组类型
   else if (type.isArray()) {
      // 确定数组元素的具体类型
      Class<?> componentType = type.getComponentType();
      // 当前依赖项的实际或期望的类型
      ResolvableType resolvableType = descriptor.getResolvableType();
      Class<?> resolvedArrayType = resolvableType.resolve(type);
      if (resolvedArrayType != type) {
         componentType = resolvableType.getComponentType().resolve();
      }
      if (componentType == null) {
         return null;
      }
      // 找到所有符合依赖项类型的所有 bean
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
            new MultiElementDescriptor(descriptor));
      if (matchingBeans.isEmpty()) {
         return null;
      }
      if (autowiredBeanNames != null) {
         // 将所有符合依赖项类型的bean放入 autowiredBeanNames 集合里
         autowiredBeanNames.addAll(matchingBeans.keySet());
      }
      TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
      // 用TypeConverter将bean集合转换成指定集合类型
      Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
      // 集合元素数量大于1,按需进行排序
      if (result instanceof Object[] array && array.length > 1) {
         Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
         if (comparator != null) {
            Arrays.sort(array, comparator);
         }
      }
      return result;
   }
   // 下面的几个跟数组类型的类似 就不多讲了
   else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
      Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
      if (elementType == null) {
         return null;
      }
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
            new MultiElementDescriptor(descriptor));
      if (matchingBeans.isEmpty()) {
         return null;
      }
      if (autowiredBeanNames != null) {
         autowiredBeanNames.addAll(matchingBeans.keySet());
      }
      TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
      Object result = converter.convertIfNecessary(matchingBeans.values(), type);
      if (result instanceof List<?> list && list.size() > 1) {
         Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
         if (comparator != null) {
            list.sort(comparator);
         }
      }
      return result;
   }
   else if (Map.class == type) {
      ResolvableType mapType = descriptor.getResolvableType().asMap();
      Class<?> keyType = mapType.resolveGeneric(0);
      if (String.class != keyType) {
         return null;
      }
      Class<?> valueType = mapType.resolveGeneric(1);
      if (valueType == null) {
         return null;
      }
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
            new MultiElementDescriptor(descriptor));
      if (matchingBeans.isEmpty()) {
         return null;
      }
      if (autowiredBeanNames != null) {
         autowiredBeanNames.addAll(matchingBeans.keySet());
      }
      return matchingBeans;
   }
   else {
      return null;
   }
}
  • 函数首先获取依赖描述符 descriptor 中表示的依赖类型。
  • 若依赖类型是 StreamDependencyDescriptor 的实例,则通过 findAutowireCandidates 方法找到所有匹配候选bean的集合。然后创建一个流,将这些bean根据名称解析为对象,并过滤掉值为 NullBean 的bean。如果依赖需要排序,则按照自定义比较器对流进行排序,最后返回这个流对象。
  • 若依赖类型是数组,函数确定数组元素的具体类型并查找相应类型的候选bean。找到后,将bean名称添加到autowiredBeanNames中(如果非空)。然后使用TypeConverter将候选bean集合转换为目标数组类型,并在满足条件时对数组元素进行排序,最后返回转换后的数组。
  • 若依赖类型是实现了 Collection 接口的类且为接口类型,函数确定集合元素的实际类型并查找匹配的bean。同样添加bean名称至autowiredBeanNames,并用TypeConverter将bean集合转换成指定集合类型。若转换后的集合元素数量大于1,按需进行排序,最后返回转换后的集合。
  • 若依赖类型是 Map 类型且键为字符串类型,函数确定Map的值类型并查找对应的bean。处理完成后,将bean名称添加至autowiredBeanNames,直接返回包含映射关系的map。
  • 对于以上情况之外的其他类型,函数返回null,表示无法解析出符合条件的bean。
  • 总之,此函数负责在Spring框架中实现复杂类型的自动装配,确保依赖项能够正确地从IoC容器中解析并初始化。

这里扩展一下,解释一下ResolvableType,如下面的代码例子

java 复制代码
@Service
public class UserService {
    private UserRepository[] userRepositoryArray;
    
    @Autowired
    public UserService(UserRepository[] repositories) {
        this.userRepositoryArray = repositories;
    }
    
    // ...
}

@Repository
public interface UserRepository {
    // ...
}

@Component("userRepository1")
public class UserRepositoryImpl1 implements UserRepository {
    // 实现方法...
}

@Component("userRepository2")
public class UserRepositoryImpl2 implements UserRepository {
    // 实现方法...
}

在上述示例中

  • ResolvableTypeDependencyDescriptor 的的作用是这样的:
  • ResolvableType: ResolvableType 是Spring框架中用于处理泛型类型的一种工具类。它提供了获取和解析复杂类型信息的能力,包括但不限于泛型参数、数组元素类型等。
    • 例如,在数组类型的注入场景中,当我们有 UserRepository[] userRepositoryArray 这样的字段时
    • descriptor.getResolvableType() 将返回一个表示该数组类型的 ResolvableType 对象。
    • 结合resolveMultipleBeans源码来解释,此时resolvableType就是UserRepository[]这个类型的泛型工具类。
    • 然后通过调用 .getComponentType().resolve() 来获取数组元素的具体类型(在这个例子中是 UserRepository 类型),所以此时componentType = resolvableType.getComponentType().resolve();就是UserRepository类型。
    • 这样做的目的是为了准确地知道应该查找哪些bean进行注入,并确保转换后的数组元素与预期类型一致。

findAutowireCandidates

在自动装配过程中查找符合要求的bean实例。

java 复制代码
 /**
  * Find bean instances that match the required type.
  * Called during autowiring for the specified bean.
  * @param beanName 即将被自动装配的bean的名称。
  * @param requiredType 需要寻找的实际类型,即要匹配的目标类型,可能是数组组件类型或集合元素类型。
  * @param descriptor 依赖描述符,用于描述当前依赖关系,如是否要求具有特定的注解 qualifier 等。
  * @throws BeansException in case of errors
  * @see #autowireByType
  * @see #autowireConstructor
  */
 protected Map<String, Object> findAutowireCandidates(
       @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
 ​
    // 候选bean名称
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
          this, requiredType, true, descriptor.isEager());
    Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
    // 尝试从缓存中获取与requiredType兼容的bean实例
    for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
       // 获取缓存中的bean 类型
       Class<?> autowiringType = classObjectEntry.getKey();
       // 类型是否与requiredType一样
       if (autowiringType.isAssignableFrom(requiredType)) {
          // 获取 bean 实例
          Object autowiringValue = classObjectEntry.getValue();
          // 析并转换给定的 autowiringValue,使其能够适配 requiredType 类型。
          // autowiringValue:这是从 resolvableDependencies 映射中获取的原始依赖值,它可以是一个bean实例、一个bean名称或者是某种表达式等,代表了某种待注入的依赖项
          // requiredType:是当前需要自动装配的目标类型,即我们期望将 autowiringValue 转换为的类型。
          autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
          // 如果autowiringValue是requiredType的类或字类的实例,那么则说明满足注入的要求,添加到结果集合中,待后续处理
          if (requiredType.isInstance(autowiringValue)) {
             result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
             break;
          }
       }
    }
    for (String candidate : candidateNames) {
       // 不是自引用 && 是自动装配候选
       if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
          // 将候选bean名称及其实例添加到结果集
          addCandidateEntry(result, candidate, descriptor, requiredType);
       }
    }
    // 没有找到多个匹配项的情况下,会进一步考虑包含自身引用的情况
    if (result.isEmpty()) {
       // 是否是接口、数组类、集合类、映射类
       boolean multiple = indicatesMultipleBeans(requiredType);
       // Consider fallback matches if the first pass failed to find anything...
       // 降级寻找依赖
       // 降级版本的依赖描述符(fallback descriptor),这个降级描述符在自动装配过程中用于处理非严格匹配的情况
       DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
       for (String candidate : candidateNames) {
          if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
                (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
             addCandidateEntry(result, candidate, descriptor, requiredType);
          }
       }
       if (result.isEmpty() && !multiple) {
          // Consider self references as a final pass...
          // but in the case of a dependency collection, not the very same bean itself.
          for (String candidate : candidateNames) {
             if (isSelfReference(beanName, candidate) &&
                   (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                   isAutowireCandidate(candidate, fallbackDescriptor)) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
             }
          }
       }
    }
    return result;
 }
  1. 使用BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法根据requiredType获取上下文中所有匹配(包括父级上下文中的)的候选bean名称,并存储到candidateNames数组中。

  2. 初始化一个LinkedHashMap result,用于存放找到的符合条件的bean实例及其名称

  3. 遍历this.resolvableDependencies映射,尝试从中找到可以直接使用的、与requiredType兼容的bean实例。如果找到并满足条件,则将其添加到结果集中,这个缓存集合是通过registerResolvableDependency,这个方法注册进来的,前面的文章有提过。

  4. 遍历第一步得到的所有候选bean名称,对每个候选bean进行以下判断:

    • 检查该bean是否为自引用(即候选bean是否与待装配bean相同),如果是则跳过。
    • 判断候选bean是否满足自动装配候选条件(通过isAutowireCandidate方法实现)。
    • 如果满足条件,则调用addCandidateEntry方法将候选bean名称及其实例添加到结果集。
  5. 若在前四步未能找到任何匹配项,则考虑备选方案(fallback匹配)。首先创建一个降级依赖描述符fallbackDescriptor,再次遍历候选bean名称列表,检查候选bean是否满足降级条件后加入结果集。

  6. 最后,在没有找到多个匹配项的情况下,会进一步考虑包含自身引用的情况,但排除直接引用自身的集合元素情况,符合条件的也加入结果集。

  7. 返回最终填充好的结果集result,其中包含了所有找到的与requiredType匹配的bean实例及其名称。

这个函数主要用于支持自动装配功能,帮助Spring容器在配置和初始化bean时,依据其依赖关系自动关联合适的bean实例。

这里解释一下第五点,什么是降级依赖描述符DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch()

java 复制代码
 @Service("importantService")
 public class ImportantServiceImpl implements ImportantService {
     // ...
 }
 ​
 @Service
 @Qualifier("fastDataSource")
 public class FastDataSource implements DataSource {
     // ...
 }
 ​
 @Service
 public class ApplicationConfig {
 ​
     @Autowired
     private ImportantService importantService;
 ​
     @Autowired
     @Qualifier("fastDataSource") // 这是一个严格匹配的数据源
     private DataSource dataSource;
 }
 ​
  • 如上面的例子,在自动装配过程中,Spring容器会为ApplicationConfig类中的字段生成对应的依赖描述符。
  • 对于importantService字段,依赖描述符将要求查找类型为ImportantService的所有候选bean,并且由于没有附加任何qualifier注解,它将尝试找到所有实现该接口的bean进行匹配。
  • 对于dataSource字段,依赖描述符不仅要求类型为DataSource,还要求具有```@Qualifier("fastDataSource")``注解。这意味着它需要寻找一个名为"fastDataSource"的或者具有相同qualifier注解的DataSource类型的bean
    • 现在,假设在某些情况下(例如,@Qualifier("fastDataSource")标注的bean不存在),无法根据原始依赖描述符找到合适的DataSource实例。
    • 这时,调用descriptor.forFallbackMatch()可能会返回一个新的降级依赖描述符,这个新的描述符可能忽略qualifier的要求,仅检查类型是否匹配,从而能够找到其他未明确指定符合数据源类型的候选bean。

总之,descriptor.forFallbackMatch()是为了在无法满足原始依赖条件时,提供一种放宽约束、扩大搜索范围以解决依赖注入问题的方式。

applyPropertyValues

主要作用是将给定的属性值(pvs)应用到由bw包装的目标bean对象上,同时解析其中可能存在的对其他bean的引用,并进行深度复制以避免对原始属性值的永久修改

java 复制代码
 protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
    if (pvs.isEmpty()) {
       return;
    }
 ​
    // 待转换的属性值列表
    MutablePropertyValues mpvs = null;
    // 原始属性值列表
    List<PropertyValue> original;
 ​
    if (pvs instanceof MutablePropertyValues _mpvs) {
       mpvs = _mpvs;
       // 如果 mpvs已经转换 则可以尝试直接使用
       if (mpvs.isConverted()) {
          // Shortcut: use the pre-converted values as-is.
          try {
             // 设置值
             bw.setPropertyValues(mpvs);
             return;
          }
          catch (BeansException ex) {
             throw new BeanCreationException(
                   mbd.getResourceDescription(), beanName, "Error setting property values", ex);
          }
       }
       // 获取原始列表值
       original = mpvs.getPropertyValueList();
    }
    else {
       original = Arrays.asList(pvs.getPropertyValues());
    }
 ​
    TypeConverter converter = getCustomTypeConverter();
    if (converter == null) {
       converter = bw;
    }
    BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
 ​
    // Create a deep copy, resolving any references for values.
    // 已转换过的放到 deepCopy中
    List<PropertyValue> deepCopy = new ArrayList<>(original.size());
    boolean resolveNecessary = false;
    for (PropertyValue pv : original) {
       if (pv.isConverted()) {
          deepCopy.add(pv);
       }
       else {
          // 获取名称
          String propertyName = pv.getName();
          // 获取原始值
          Object originalValue = pv.getValue();
          // 判断是否需要自动装配
          if (originalValue == AutowiredPropertyMarker.INSTANCE) {
             // 获取 set 方法
             Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();
             // 因为自动装配是通过 set 方法进行注入的,set方法为 null 则直接抛异常
             if (writeMethod == null) {
                throw new IllegalArgumentException("Autowire marker for property without write method: " + pv);
             }
             originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true);
          }
          Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
          Object convertedValue = resolvedValue;
          boolean convertible = bw.isWritableProperty(propertyName) &&
                !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
          // 完成转换的值
          if (convertible) {
             convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
          }
          // Possibly store converted value in merged bean definition,
          // in order to avoid re-conversion for every created bean instance.
          if (resolvedValue == originalValue) {
             if (convertible) {
                pv.setConvertedValue(convertedValue);
             }
             deepCopy.add(pv);
          }
          else if (convertible && originalValue instanceof TypedStringValue typedStringValue &&
                !typedStringValue.isDynamic() &&
                !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
             pv.setConvertedValue(convertedValue);
             deepCopy.add(pv);
          }
          else {
             resolveNecessary = true;
             deepCopy.add(new PropertyValue(pv, convertedValue));
          }
       }
    }
    if (mpvs != null && !resolveNecessary) {
       mpvs.setConverted();
    }
 ​
    // Set our (possibly massaged) deep copy.
    // 设置值
    try {
       bw.setPropertyValues(new MutablePropertyValues(deepCopy));
    }
    catch (BeansException ex) {
       throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
    }
 }
  1. 首先检查提供的PropertyValues实例(pvs)是否为空。如果为空,则直接返回。

  2. 如果pvs非空,根据其类型创建或获取一个可变的MutablePropertyValues实例(mpvs),并获取原始属性值列表。

  3. 判断pvs是否已经转换过,如果是且已转换完成,则尝试直接使用转换后的属性值通过bw.setPropertyValues(mpvs)设置到目标bean上,若出现异常则抛出BeanCreationException。

  4. 对于未转换或需要进一步处理的属性值,创建一个新的深度复制列表(deepCopy),并遍历原始属性值:

    • 检查每个属性值是否已转换,已转换的直接添加到deepCopy中。
    • 对于未转换的属性值,调用BeanDefinitionValueResolver来解析和解决可能存在的bean引用或其他运行时表达式
    • writeMethod == nul这里会抛异常,是因为 spring 的自动装配是通过 set 方法完成的,有些也可以通过构造器注入,所以这里所以没有暴露 set 方法,就直接抛异常。之前我在这里就有疑问?
      • 我们在日常开发的时候,经常在写 service 的时候,直接一个@Autowire 注解搞定,什么都没有写,这是怎么注入的呢?
      • 这是因为Spring默认支持字段级别的自动注入。当Spring容器初始化一个bean时,它会检测该bean的所有字段,并查找带有@Autowired、@Inject等注解的字段,然后根据类型或名称从IoC容器中找到匹配的bean进行注入。
    • 根据需要进行类型转换,利用TypeConverter将解析后的值转换为目标bean属性期望的类型。
    • 将转换后的值存储回PropertyValue实例,标记为已转换,并添加到deepCopy中。
  5. 最后,使用包含所有已处理过的属性值的新MutablePropertyValues实例(基于deepCopy)来设置目标bean的属性值,如果在此过程中出现异常,同样抛出BeanCreationException。

这个函数主要服务于Spring框架在初始化bean的过程中,处理bean定义中的属性注入逻辑

至此,spring 的自动装配已经完成啦!接下来就是 bean 的初始化了。

总结

populateBean方法主要用于完成对@Autowired、@Resource、@Value等注解标注的属性的填充。

流程:

Spring通过createBeanInstance方法创建了对象。 在对象被创建完成后,调用了populateBean方法。 populateBean方法会对@Autowired、@Resource、@Value等注解标注的属性进行填充。

相关推荐
向前看-2 小时前
验证码机制
前端·后端
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
AskHarries5 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion6 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp7 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder7 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚8 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心9 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴9 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲9 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端