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等注解标注的属性进行填充。

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH4 小时前
Koa框架的使用
后端
luckdewei5 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github