這篇文章,咱們來談一談Spring中的屬性注入


本系列文章:
java

讀源碼,咱們能夠從第一行讀起
程序員

你知道Spring是怎麼解析配置類的嗎?
web

配置類爲何要添加@Configuration註解?
spring

談談Spring中的對象跟Bean,你知道Spring怎麼建立對象的嗎?
數組

推薦閱讀:
緩存

Spring官網閱讀 | 總結篇
微信

Spring雜談
app

本系列文章將會帶你一行行的將Spring的源碼吃透,推薦閱讀的文章是閱讀源碼的基礎!編輯器

前言

在前面的文章中已經知道了Spring是如何將一個對象建立出來的,那麼緊接着,Spring就須要將這個對象變成一個真正的Bean了,這個過程主要分爲兩步ide

  1. 屬性注入
  2. 初始化

在這兩個過程當中,Bean的後置處理器會穿插執行,其中有些後置處理器是爲了幫助完成屬性注入或者初始化的,而有些後置處理器是Spring提供給程序員進行擴展的,固然,這兩者並不衝突。整個Spring建立對象並將對象變成Bean的過程就是咱們常常提到了Spring中Bean的生命週期。固然,本系列源碼分析的文章不會再對生命週期的概念作過多闡述了,若是你們有這方面的需求的話能夠參考我以前的文章,或者關注個人公衆號:程序員DMZ

Spring官網閱讀(九)Spring中Bean的生命週期(上)

Spring官網閱讀(十)Spring中Bean的生命週期(下)

源碼分析

閒話再也不多說,咱們正式進入源碼分析階段,本文重點要分析的方法就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,其源碼以下:

doCreateBean

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

  // 建立對象的過程在上篇文章中咱們已經介紹過了,這裏再也不贅述
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
   instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
   instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
        
        // 獲取到建立的這個對象
  final Object bean = instanceWrapper.getWrappedInstance();
  Class<?> beanType = instanceWrapper.getWrappedClass();
  if (beanType != NullBean.class) {
   mbd.resolvedTargetType = beanType;
  }

  // Allow post-processors to modify the merged bean definition.
        // 按照官方的註釋來講,這個地方是Spring提供的一個擴展點,對程序員而言,咱們能夠經過一個實現了MergedBeanDefinitionPostProcessor的後置處理器來修改bd中的屬性,從而影響到後續的Bean的生命週期
        // 不過官方本身實現的後置處理器並無去修改bd,而是調用了applyMergedBeanDefinitionPostProcessors方法
        // 這個方法名直譯過來就是-應用合併後的bd,也就是說它這裏只是對bd作了進一步的使用而沒有真正的修改
  synchronized (mbd.postProcessingLock) {
           // bd只容許被處理一次
   if (!mbd.postProcessed) {
    try {
                    // 應用合併後的bd
     applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
    }
    catch (Throwable ex) {
     throw new BeanCreationException(mbd.getResourceDescription(), beanName,
       "Post-processing of merged bean definition failed", ex);
    }
                // 標註這個bd已經被MergedBeanDefinitionPostProcessor的後置處理器處理過
                // 那麼在第二次建立Bean的時候,不會再次調用applyMergedBeanDefinitionPostProcessors
    mbd.postProcessed = true;
   }
  }

  // 這裏是用來出來循環依賴的,關於循環以來,在介紹完正常的Bean的建立後,單獨用一篇文章說明
        // 這裏不作過多解釋
  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));
  }


  Object exposedObject = bean;
  try {
            // 咱們這篇文章重點要分析的就是populateBean方法,在這個方法中完成了屬性注入
   populateBean(beanName, mbd, instanceWrapper);
            // 初始化
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  catch (Throwable ex) {
   // 省略異常代碼
  }

  // 後續代碼不在本文探討範圍內了,暫不考慮

  return exposedObject;
 }

applyMergedBeanDefinitionPostProcessors

源碼以下:

// 能夠看到這個方法的代碼仍是很簡單的,就是調用了MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof MergedBeanDefinitionPostProcessor) {
            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
        }
    }
}

這個時候咱們就要思考一個問題,容器中如今有哪些後置處理器是MergedBeanDefinitionPostProcessor呢?

image-20200613200058693

查看這個方法的實現類咱們會發現總共就這麼幾個類實現了MergedBeanDefinitionPostProcessor接口。實際上除了ApplicationListenerDetector以外,其他的後置處理器的邏輯都差很少。咱們在這裏咱們主要就分析兩個後置處理

  1. ApplicationListenerDetector
  2. AutowiredAnnotationBeanPostProcessor

ApplicationListenerDetector

首先,咱們來ApplicationListenerDetector,這個類在以前的文章中也屢次提到過了,它的做用是用來處理嵌套Bean的狀況,主要是保證能將嵌套在Bean標籤中的ApplicationListener也能添加到容器的監聽器集合中去。咱們先經過一個例子來感覺下這個後置處理器的做用吧

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


 <bean class="com.dmz.source.populate.service.DmzService" id="dmzService">
  <constructor-arg name="orderService">
   <bean class="com.dmz.source.populate.service.OrderService"/>
  </constructor-arg>
 </bean>
</beans>

示例代碼:

// 事件
public class DmzEvent extends ApplicationEvent {
 public DmzEvent(Object source) {
  super(source);
 }
}

public class DmzService {

 OrderService orderService;

 public DmzService(OrderService orderService) {
  this.orderService = orderService;
 }
}
// 實現ApplicationListener接口
public class OrderService implements ApplicationListener<DmzEvent{
 @Override
 public void onApplicationEvent(DmzEvent event) {
  System.out.println(event.getSource());
 }
}

public class Main {
 public static void main(String[] args) {
  ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application-populate.xml");
  cc.publishEvent(new DmzEvent("my name is dmz"));
 }
}

// 程序運行結果,控制檯打印:my name is dmz

說明OrderService已經被添加到了容器的監聽器集合中。可是請注意,在這種狀況下,若是要使OrderService可以執行監聽的邏輯,必需要知足下面這兩個條件

  • 外部的Bean要是單例的,對於咱們的例子而言就是dmzService
  • 內嵌的Bean也必須是單例的,在上面的例子中也就是orderService必須是單例

另外須要注意的是,這種嵌套的Bean比較特殊,它雖然由Spring建立,可是確不存在於容器中,就是說咱們不能將其做爲依賴注入到別的Bean中。

AutowiredAnnotationBeanPostProcessor

對應源碼以下:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 找到注入的元數據,第一次是構建,後續能夠直接從緩存中拿
    // 註解元數據其實就是當前這個類中的全部須要進行注入的「點」的集合,
    // 注入點(InjectedElement)包含兩種,字段/方法
    // 對應的就是AutowiredFieldElement/AutowiredMethodElement
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 排除掉被外部管理的注入點
    metadata.checkConfigMembers(beanDefinition);
}

上面代碼的核心邏輯就是

  • 找到全部的注入點,其實就是被@Autowired註解修飾的方法以及字段,同時靜態的方法以及字段也會被排除
  • 排除掉被外部管理的注入點,在後續的源碼分析中咱們再細說

findAutowiringMetadata

// 這個方法的核心邏輯就是先從緩存中獲取已經解析好的注入點信息,很明顯,在原型狀況下才會使用緩存
// 建立注入點的核心邏輯在buildAutowiringMetadata方法中
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 可能咱們會修改bd中的class屬性,那麼InjectionMetadata中的注入點信息也須要刷新
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 這裏真正建立注入點
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

buildAutowiringMetadata

// 咱們應用中使用@Autowired註解標註在字段上或者setter方法可以完成屬性注入
// 就是由於這個方法將@Autowired註解標註的方法以及字段封裝成InjectionMetadata
// 在後續階段會調用InjectionMetadata的inject方法進行注入
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
  // 處理全部的被@AutoWired/@Value註解標註的字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            AnnotationAttributes ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 靜態字段會直接跳過
                if (Modifier.isStatic(field.getModifiers())) {
                    // 省略日誌打印
                    return;
                }
                // 獲得@AutoWired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
  // 處理全部的被@AutoWired註解標註的方法,相對於字段而言,這裏須要對橋接方法進行特殊處理
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 只處理一種特殊的橋接場景,其他的橋接方法都會被忽略
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
            // 處理方法時須要注意,當父類中的方法被子類重寫時,若是子父類中的方法都加了@Autowired
            // 那麼此時父類方法不能被處理,即不能被封裝成一個AutowiredMethodElement
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    // 省略日誌打印
                    return;
                }
                if (method.getParameterCount() == 0) {
                    // 當方法的參數數量爲0時,雖然不須要進行注入,可是仍是會把這個方法做爲注入點使用
                    // 這個方法最終仍是會被調用
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                    }
                }
                boolean required = determineRequiredStatus(ann);
                // PropertyDescriptor: 屬性描述符
                // 就是經過解析getter/setter方法,例如void getA()會解析獲得一個屬性名稱爲a
                // readMethod爲getA的PropertyDescriptor,
                // 在《Spring官網閱讀(十四)Spring中的BeanWrapper及類型轉換》文中已經作過解釋
                // 這裏再也不贅述,這裏之因此來這麼一次查找是由於當XML中對這個屬性進行了配置後,
                // 那麼就不會進行自動注入了,XML中顯示指定的屬性優先級高於註解
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);     // 構造一個對應的AutowiredMethodElement,後續這個方法會被執行
                // 方法的參數會被自動注入,這裏不限於setter方法
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
  // 會處理父類中字段上及方法上的@AutoWired註解,而且父類的優先級比子類高
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);

    return new InjectionMetadata(clazz, elements);
}
難點代碼分析

上面的代碼總體來講應該很簡單,就如咱們以前所說的,處理帶有@Autowired註解的字段及方法,同時會過濾掉全部的靜態字段及方法。上面複雜的地方在於對橋接方法的處理,可能大部分人都沒辦法理解這幾行代碼:

// 第一行
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);

// 第二行
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
}

// 第三行
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {

}

要理解這些代碼,首先你得知道什麼是橋接,爲此我已經寫好了一篇文章:

Spring雜談 | 從橋接方法到JVM方法調用

除了在上面的文章中提到的橋接方法外,還有一種特殊的狀況

// A類跟B類在同一個包下,A不是public的
class A {
 public void test(){

 }
}

// 在B中會生成一個跟A中的方法描述符(參數+返回值)如出一轍的橋接方法
// 這個橋接方法實際上就是調用父類中的方法
// 具體能夠參考:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=63424113
public class B extends A {
}

在理解了什麼是橋接以後,那麼上邊的第一行代碼你應該就能看懂了,就以上面的代碼爲例,B中會生成一個橋接方法,對應的被橋接的方法就是A中的test方法。

接着,咱們看看第二行代碼

public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
    // 說明這個方法自己就不是橋接方法,直接返回true
    if (bridgeMethod == bridgedMethod) {
        return true;
    }
    // 說明是橋接方法,而且方法描述符一致
    // 當且僅當是上面例子中描述的這種橋接的時候這個判斷纔會知足
    // 正常來講橋接方法跟被橋接方法的返回值+參數類型確定不一致
    // 因此這個判斷會過濾掉其他的全部類型的橋接方法
    // 只會保留本文說起這種特殊狀況下產生的橋接方法
    return (bridgeMethod.getReturnType().equals(bridgedMethod.getReturnType()) &&
            Arrays.equals(bridgeMethod.getParameterTypes(), bridgedMethod.getParameterTypes()));
}

最後,再來看看第三行代碼,核心就是這句method.equals(ClassUtils.getMostSpecificMethod(method, clazz)。這句代碼的主要目的就是爲了處理下面這種狀況

@Component
public class D extends C {

 @Autowired
 @Override
 public void setDmzService(DmzService dmzService) {
  dmzService.init();
  this.dmzService = dmzService;
 }
}

// C不是Spring中的組件
public class C {
 DmzService dmzService;
    @Autowired
 public void setDmzService(DmzService dmzService) {
  this.dmzService = dmzService;
 }
}

這種狀況下,在處理D中的@Autowired註解時,雖然咱們要處理父類中的@Autowired註解,可是由於子類中的方法已經複寫了父類中的方法,因此此時應該要跳過父類中的這個被複寫的方法,這就是第三行代碼的做用。

小結

到這裏咱們主要分析了applyMergedBeanDefinitionPostProcessors這段代碼的做用,它的執行時機是在建立對象以後,屬性注入以前。按照官方的定義來講,到這裏咱們仍然可使用這個方法來修改bd的定義,那麼相對於經過BeanFactoryPostProcessor的方式修改bd,applyMergedBeanDefinitionPostProcessors這個方法影響的範圍更小,BeanFactoryPostProcessor影響的是整個Bean的生命週期,而applyMergedBeanDefinitionPostProcessors只會影響屬性注入以後的生命週期。

其次,咱們分析了Spring中內置的MergedBeanDefinitionPostProcessor,選取了其中兩個特殊的後置處理器進行分析,其中ApplicationListenerDetector主要處理內嵌的事件監聽器,而AutowiredAnnotationBeanPostProcessor主要用於處理@Autowired註解,實際上咱們會發現,到這裏還只是完成了@Autowired註解的解析,尚未真正開始進行注入,真正注入的邏輯在後面咱們要分析的populateBean方法中,在這個方法中會使用解析好的注入元信息完成真正的屬性注入,那麼接下來咱們就開始分析populateBean這個方法的源碼。

populateBean

循環依賴的代碼咱們暫且跳過,後續出一篇專門文章解讀循環依賴,咱們直接看看populateBean到底作了什麼。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

    // 處理空實例
    if (bw == null) {
        // 若是建立的對象爲空,可是在XML中又配置了須要注入的屬性的話,那麼直接報錯
        if (mbd.hasPropertyValues()) {
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
        }
        else {
            // 空對象,不進行屬性注入
            return;
        }
    }

    // 知足兩個條件,不是合成類 && 存在InstantiationAwareBeanPostProcessor
    // 其中InstantiationAwareBeanPostProcessor主要做用就是做爲Bean的實例化先後的鉤子
    // 外加完成屬性注入,對於三個方法就是
    // postProcessBeforeInstantiation  建立對象前調用
    // postProcessAfterInstantiation   對象建立完成,@AutoWired註解解析後調用   
    // postProcessPropertyValues(已過時,被postProcessProperties替代) 進行屬性注入
    // 下面這段代碼的主要做用就是咱們能夠提供一個InstantiationAwareBeanPostProcessor
    // 提供的這個後置處理若是實現了postProcessAfterInstantiation方法而且返回false
    // 那麼能夠跳過Spring默認的屬性注入,可是這也意味着咱們要本身去實現屬性注入的邏輯
    // 因此通常狀況下,咱們也不會這麼去擴展
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }
    }
 
    // 這裏其實就是判斷XML是否提供了屬性相關配置
    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
 
    // 確認注入模型
    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
    
    // 主要處理byName跟byType兩種注入模型,byConstructor這種注入模型在建立對象的時候已經處理過了
    // 這裏都是對自動注入進行處理,byName跟byType兩種注入模型均是依賴setter方法
    // byName,根據setter方法的名字來查找對應的依賴,例如setA,那麼就是去容器中查找名字爲a的Bean
    // byType,根據setter方法的參數類型來查找對應的依賴,例如setXx(A a),就是去容器中查詢類型爲A的bean
    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
        if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
            autowireByName(beanName, mbd, bw, newPvs);
        }
        if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
            autowireByType(beanName, mbd, bw, newPvs);
        }
        // pvs是XML定義的屬性
        // 自動注入後,bean實際用到的屬性就應該要替換成自動注入後的屬性
        pvs = newPvs;
    }
 // 檢查是否有InstantiationAwareBeanPostProcessor
    // 前面說過了,這個後置處理器就是來完成屬性注入的
    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    
    //  是否須要依賴檢查,默認是不會進行依賴檢查的
    boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
 
    // 下面這段代碼有點麻煩了,由於涉及到版本問題
    // 其核心代碼就是調用了postProcessProperties完成了屬性注入
   
    PropertyDescriptor[] filteredPds = null;
    
    // 存在InstantiationAwareBeanPostProcessor,咱們須要調用這類後置處理器的方法進行注入
  if (hasInstAwareBpps) {
   if (pvs == null) {
    pvs = mbd.getPropertyValues();
   }
   for (BeanPostProcessor bp : getBeanPostProcessors()) {
    if (bp instanceof InstantiationAwareBeanPostProcessor) {
     InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    // 這句就是核心
     PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
     if (pvsToUse == null) {
      if (filteredPds == null) {
                            // 獲得須要進行依賴檢查的屬性的集合
       filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
                        //  這個方法已通過時了,放到這裏就是爲了兼容老版本
      pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
      if (pvsToUse == null) {
       return;
      }
     }
     pvs = pvsToUse;
    }
   }
  }
    // 須要進行依賴檢查
  if (needsDepCheck) {
   if (filteredPds == null) {
                // 獲得須要進行依賴檢查的屬性的集合
    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
   }
            // 對須要進行依賴檢查的屬性進行依賴檢查
   checkDependencies(beanName, mbd, filteredPds, pvs);
  }
    // 將XML中的配置屬性應用到Bean上
  if (pvs != null) {
   applyPropertyValues(beanName, mbd, bw, pvs);
  }
}

上面這段代碼主要能夠拆分爲三個部分

  1. 處理自動注入
  2. 處理屬性注入(主要指處理@Autowired註解),最重要
  3. 處理依賴檢查

處理自動注入

autowireByName

對應源碼以下:

protected void autowireByName(
    String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)
 
{
    // 獲得符合下面條件的屬性名稱
    // 1.有setter方法
    // 2.須要進行依賴檢查
    // 3.不包含在XML配置中
    // 4.不是簡單類型(基本數據類型,枚舉,日期等)
    // 這裏能夠看到XML配置優先級高於自動注入的優先級
    // 不進行依賴檢查的屬性,也不會進行屬性注入
    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
    for (String propertyName : propertyNames) {
        if (containsBean(propertyName)) {
            Object bean = getBean(propertyName);
            // 將自動注入的屬性添加到pvs中去
            pvs.add(propertyName, bean);
            // 註冊bean之間的依賴關係
            registerDependentBean(propertyName, beanName);
            // 忽略日誌
        }
        // 忽略日誌
    }
}

看到了嗎?代碼就是這麼的簡單,不是要經過名稱注入嗎?直接經過beanName調用getBean,完事兒

autowireByType

 protected void autowireByType(
   String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)
 
{
  // 這個類型轉換器,主要是在處理@Value時須要使用
  TypeConverter converter = getCustomTypeConverter();
  if (converter == null) {
   converter = bw;
  }

  Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
  // 獲得符合下面條件的屬性名稱
  // 1.有setter方法
  // 2.須要進行依賴檢查
  // 3.不包含在XML配置中
  // 4.不是簡單類型(基本數據類型,枚舉,日期等)
  // 這裏能夠看到XML配置優先級高於自動注入的優先級
  String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  for (String propertyName : propertyNames) {
   try {
    PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
    if (Object.class != pd.getPropertyType()) {
     // 這裏獲取到的就是setter方法的參數,由於咱們須要按照類型進行注入嘛
     MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
     
                    // 若是是PriorityOrdered在進行類型匹配時不會去匹配factoryBean
     // 若是不是PriorityOrdered,那麼在查找對應類型的依賴的時候會會去匹factoryBean
      // 這就是Spring的一種設計理念,實現了PriorityOrdered接口的Bean被認爲是一種
                    // 最高優先級的Bean,這一類的Bean在進行爲了完成裝配而去檢查類型時,
                    // 不去檢查factoryBean
                    // 具體能夠參考PriorityOrdered接口上的註釋文檔
     boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
     // 將參數封裝成爲一個依賴描述符
     // 依賴描述符會經過:依賴所在的類,字段名/方法名,依賴的具體類型等來描述這個依賴
     DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
     // 解析依賴,這裏會處理@Value註解
                    // 另外,經過指定的類型到容器中查找對應的bean
     Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
     if (autowiredArgument != null) {
      // 將查找出來的依賴屬性添加到pvs中,後面會將這個pvs應用到bean上
      pvs.add(propertyName, autowiredArgument);
     }
     // 註冊bean直接的依賴關係
     for (String autowiredBeanName : autowiredBeanNames) {
      registerDependentBean(autowiredBeanName, beanName);
      if (logger.isDebugEnabled()) {
       logger.debug("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);
   }
  }
 }

resolveDependency

這個方法在Spring雜談 | 什麼是ObjectFactory?什麼是ObjectProvider?已經作過度析了,本文再也不贅述。

能夠看到,真正作事的方法是doResolveDependency

@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
 // descriptor表明當前須要注入的那個字段,或者方法的參數,也就是注入點
    // ParameterNameDiscovery用於解析方法參數名稱
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 1. Optional<T>
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    // 2. ObjectFactory<T>、ObjectProvider<T>
    } else if (ObjectFactory.class == descriptor.getDependencyType() ||
             ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    // 3. javax.inject.Provider<T>
    } else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    } else {
        // 4. @Lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
        // 5. 正常狀況
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}
doResolveDependency
 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中的值
   Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
   if (value != null) {
    if (value instanceof String) {
     // 解析@Value中的佔位符
     String strVal = resolveEmbeddedValue((String) value);
     // 獲取到對應的bd
     BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
     // 處理EL表達式
     value = evaluateBeanDefinitionString(strVal, bd);
    }
    // 經過解析el表達式可能還須要進行類型轉換
    TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
    return (descriptor.getField() != null ?
      converter.convertIfNecessary(value, type, descriptor.getField()) :
      converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
   }
   
            // 對map,collection,數組類型的依賴進行處理
   // 最終會根據集合中的元素類型,調用findAutowireCandidates方法
   Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
   if (multipleBeans != null) {
    return multipleBeans;
   }
   
            // 根據指定類型可能會找到多個bean
            // 這裏返回的既有多是對象,也有多是對象的類型
            // 這是由於到這裏還不能明確的肯定當前bean到底依賴的是哪個bean
            // 因此若是隻會返回這個依賴的類型以及對應名稱,最後還須要調用getBean(beanName)
            // 去建立這個Bean
   Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
   // 一個都沒找到,直接拋出異常
   if (matchingBeans.isEmpty()) {
    if (isRequired(descriptor)) {
     raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
    }
    return null;
   }

   String autowiredBeanName;
   Object instanceCandidate;
   // 經過類型找到了多個
   if (matchingBeans.size() > 1) {
    // 根據是不是主Bean
    // 是不是最高優先級的Bean
    // 是不是名稱匹配的Bean
    // 來肯定具體的須要注入的Bean的名稱
                // 到這裏能夠知道,Spring在查找依賴的時候遵循先類型再名稱的原則(沒有@Qualifier註解狀況下)
    autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
    if (autowiredBeanName == null) {
     // 沒法推斷出具體的名稱
     // 若是依賴是必須的,直接拋出異常
     // 若是依賴不是必須的,可是這個依賴類型不是集合或者數組,那麼也拋出異常
     if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
      return descriptor.resolveNotUnique(type, matchingBeans);
     }
     // 依賴不是必須的,可是依賴類型是集合或者數組,那麼返回一個null
     else {
      return null;
     }
    }
    instanceCandidate = matchingBeans.get(autowiredBeanName);
   }
   else {
    // 直接找到了一個對應的Bean
    Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
    autowiredBeanName = entry.getKey();
    instanceCandidate = entry.getValue();
   }
   if (autowiredBeanNames != null) {
    autowiredBeanNames.add(autowiredBeanName);
   }
            
            // 前面已經說過了,這裏可能返回的是Bean的類型,因此須要進一步調用getBean
   if (instanceCandidate instanceof Class) {
    instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
   }
            
            // 作一些檢查,若是依賴是必須的,查找出來的依賴是一個null,那麼報錯
            // 查詢處理的依賴類型不符合,也報錯
   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);
  }
 }
findAutowireCandidates
protected Map<String, Object> findAutowireCandidates(
    @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor)
 
{
 
    // 簡單來講,這裏就是到容器中查詢requiredType類型的全部bean的名稱的集合
    // 這裏會根據descriptor.isEager()來決定是否要匹配factoryBean類型的Bean
    // 若是isEager()爲true,那麼會匹配factoryBean,反之,不會
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
        this, requiredType, true, descriptor.isEager());
   
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
   
    // 第一步會到resolvableDependencies這個集合中查詢是否已經存在瞭解析好的依賴
    // 像咱們之因此可以直接在Bean中注入applicationContext對象
    // 就是由於Spring以前就將這個對象放入了resolvableDependencies集合中
    for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = this.resolvableDependencies.get(autowiringType);
            
            // 若是resolvableDependencies放入的是一個ObjectFactory類型的依賴
            // 那麼在這裏會生成一個代理對象
            // 例如,咱們能夠在controller中直接注入request對象
            // 就是由於,容器啓動時就在resolvableDependencies放入了一個鍵值對
            // 其中key爲:Request.class,value爲:ObjectFactory
            // 在實際注入時放入的是一個代理對象
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                // 這裏放入的key不是Bean的名稱
                // value是實際依賴的對象
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    
    // 接下來開始對以前查找出來的類型匹配的全部BeanName進行處理
    for (String candidate : candidateNames) {
        // 不是自引用,什麼是自引用?
        // 1.候選的Bean的名稱跟須要進行注入的Bean名稱相同,意味着,本身注入本身
        // 2.或者候選的Bean對應的factoryBean的名稱跟須要注入的Bean名稱相同,
        // 也就是說A依賴了B可是B的建立又須要依賴A
        // 要符合注入的條件
        if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
            // 調用addCandidateEntry,加入到返回集合中,後文有對這個方法的分析
            addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
    
    // 排除自引用的狀況下,沒有找到一個合適的依賴
    if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
        // 1.先走fallback邏輯,Spring提供的一個擴展吧,感受沒什麼卵用
        // 默認狀況下fallback的依賴描述符就是自身
        DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
        for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
            }
        }
        // fallback仍是失敗
        if (result.isEmpty()) {
            // 處理自引用
            // 從這裏能夠看出,自引用的優先級是很低的,只有在容器中真正的只有這個Bean能做爲
            // 候選者的時候,纔會去處理,不然自引用是被排除掉的
            for (String candidate : candidateNames) {
                if (isSelfReference(beanName, candidate) &&
                    // 不是一個集合或者
                    // 是一個集合,可是beanName跟candidate的factoryBeanName相同
                    (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                    isAutowireCandidate(candidate, fallbackDescriptor)) {
                    addCandidateEntry(result, candidate, descriptor, requiredType);
                }
            }
        }
    }
    return result;
}


// candidates:就是findAutowireCandidates方法要返回的候選集合
// candidateName:當前的這個候選Bean的名稱
// descriptor:依賴描述符
// requiredType:依賴的類型
private void addCandidateEntry(Map<String, Object> candidates, String candidateName,
                               DependencyDescriptor descriptor, Class<?> requiredType)
 
{
 
    // 若是依賴是一個集合,或者容器中已經包含這個單例了
    // 那麼直接調用getBean方法建立或者獲取這個Bean
    if (descriptor instanceof MultiElementDescriptor || containsSingleton(candidateName)) {
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
    }
    // 若是依賴的類型不是一個集合,這個時候還不能肯定到底要使用哪一個依賴,
    // 因此不能將這些Bean建立出來,因此這個時候,放入candidates是Bean的名稱以及類型
    else {
        candidates.put(candidateName, getType(candidateName));
    }
}

處理屬性注入(@Autowired)

postProcessProperties

// 在applyMergedBeanDefinitionPostProcessors方法執行的時候,
// 已經解析過了@Autowired註解(buildAutowiringMetadata方法)
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 這裏獲取到的是解析過的緩存好的注入元數據
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 直接調用inject方法
        // 存在兩種InjectionMetadata
        // 1.AutowiredFieldElement
        // 2.AutowiredMethodElement
        // 分別對應字段的屬性注入以及方法的屬性注入
        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;
}
字段的屬性注入
// 最終反射調用filed.set方法
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        // 第一次注入的時候確定沒有緩存
        // 這裏也是對原型狀況的處理
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    } else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null"No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
            // 這裏能夠看到,對@Autowired註解在字段上的處理
            // 跟byType下自動注入的處理是同樣的,就是調用resolveDependency方法
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        } catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        synchronized (this) {
            // 沒有緩存過的話,這裏須要進行緩存
            if (!this.cached) {
                if (value != null || this.required) {
                    this.cachedFieldValue = desc;
                    // 註冊Bean之間的依賴關係
                    registerDependentBeans(beanName, autowiredBeanNames);
                    // 若是這個類型的依賴只存在一個的話,咱們就能肯定這個Bean的名稱
                    // 那麼直接將這個名稱緩存到ShortcutDependencyDescriptor中
                    // 第二次進行注入的時候就能夠直接調用getBean(beanName)獲得這個依賴了
                    // 實際上正常也只有一個,多個就報錯了
                    // 另外這裏會過濾掉@Vlaue獲得的依賴
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        // 經過resolvableDependencies這個集合找的依賴不知足containsBean條件
                        // 不會進行緩存,由於緩存實際仍是要調用getBean,而resolvableDependencies
                        // 是無法經過getBean獲取的
                        if (beanFactory.containsBean(autowiredBeanName) &&
                            beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {        // 依賴描述符封裝成ShortcutDependencyDescriptor進行緩存
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                desc, autowiredBeanName, field.getType());
                        }
                    }
                } else {
                    this.cachedFieldValue = null;
                }
                this.cached = true;
            }
        }
    }
    if (value != null) {
        // 反射調用Field.set方法
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}
方法的屬性注入
// 代碼看着很長,實際上邏輯跟字段注入基本同樣
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 判斷XML中是否配置了這個屬性,若是配置了直接跳過
    // 換而言之,XML配置的屬性優先級高於@Autowired註解
    if (checkPropertySkipping(pvs)) {
        return;
    }
    Method method = (Method) this.member;
    Object[] arguments;
    if (this.cached) {
        arguments = resolveCachedArguments(beanName);
    } else {
        // 經過方法參數類型構造依賴描述符
        // 邏輯基本同樣的,最終也是調用beanFactory.resolveDependency方法
        Class<?>[] paramTypes = method.getParameterTypes();
        arguments = new Object[paramTypes.length];
        DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
        Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length);
        Assert.state(beanFactory != null"No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        
        // 遍歷方法的每一個參數
        for (int i = 0; i < arguments.length; i++) {
            MethodParameter methodParam = new MethodParameter(method, i);
            DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
            currDesc.setContainingClass(bean.getClass());
            descriptors[i] = currDesc;
            try {
                // 仍是要調用這個方法
                Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                if (arg == null && !this.required) {
                    arguments = null;
                    break;
                }
                arguments[i] = arg;
            } catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
            }
        }
        synchronized (this) {
            if (!this.cached) {
                if (arguments != null) {
                    Object[] cachedMethodArguments = new Object[paramTypes.length];
                    System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length);  
                    // 註冊bean之間的依賴關係
                    registerDependentBeans(beanName, autowiredBeans);
                    
                    // 跟字段注入差很少,存在@Value註解,不進行緩存
                    if (autowiredBeans.size() == paramTypes.length) {
                        Iterator<String> it = autowiredBeans.iterator();
                        for (int i = 0; i < paramTypes.length; i++) {
                            String autowiredBeanName = it.next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
                                cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
                                    descriptors[i], autowiredBeanName, paramTypes[i]);
                            }
                        }
                    }
                    this.cachedMethodArguments = cachedMethodArguments;
                } else {
                    this.cachedMethodArguments = null;
                }
                this.cached = true;
            }
        }
    }
    if (arguments != null) {
        try {
            // 反射調用方法
            // 像咱們的setter方法就是在這裏調用的
            ReflectionUtils.makeAccessible(method);
            method.invoke(bean, arguments);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

處理依賴檢查

protected void checkDependencies(
    String beanName, AbstractBeanDefinition mbd, PropertyDescriptor[] pds, PropertyValues pvs)

    throws UnsatisfiedDependencyException 
{

    int dependencyCheck = mbd.getDependencyCheck();
    for (PropertyDescriptor pd : pds) {
        
        // 有set方法可是在pvs中沒有對應屬性,那麼須要判斷這個屬性是否要進行依賴檢查
        // 若是須要進行依賴檢查的話,就須要報錯了
        // pvs中保存的是自動注入以及XML配置的屬性
        if (pd.getWriteMethod() != null && !pvs.contains(pd.getName())) {
           
            // 是不是基本屬性,枚舉/日期等也包括在內
            boolean isSimple = BeanUtils.isSimpleProperty(pd.getPropertyType());
            
            // 若是DEPENDENCY_CHECK_ALL,對任意屬性都開啓了依賴檢查,報錯
            // DEPENDENCY_CHECK_SIMPLE,對基本屬性開啓了依賴檢查而且是基本屬性,報錯
            // DEPENDENCY_CHECK_OBJECTS,對非基本屬性開啓了依賴檢查而且不是非基本屬性,報錯
            boolean unsatisfied = (dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_ALL) ||
                (isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_SIMPLE) ||
                (!isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS);
            
            if (unsatisfied) {
                throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, pd.getName(),
                                                         "Set this property value or disable dependency checking for this bean.");
            }
        }
    }
}

將解析出來的屬性應用到Bean上

到這一步解析出來的屬性主要有三個來源

  1. XML中配置的
  2. 經過byName的方式自動注入的
  3. 經過byType的方式自動注入的

可是在應用到Bean前還須要作一步類型轉換,這一部分代碼實際上跟咱們以前在Spring官網閱讀(十四)Spring中的BeanWrapper及類型轉換介紹的差很少,並且由於XML跟自動注入的方式都不常見,正常@Autowired的方式進行注入的話,這個方法沒有什麼用,因此本文就再也不贅述。

總結

本文咱們主要分析了Spring在屬性注入過程當中的相關代碼,整個屬性注入能夠分爲兩個部分

  1. @Autowired/ @Vale的方式完成屬性注入
  2. 自動注入( byType/ byName

完成屬性注入的核心方法其實就是doResolveDependencydoResolveDependency這個方法的邏輯簡單來講分爲兩步:

  1. 經過依賴類型查詢到全部的類型匹配的bean的名稱
  2. 若是找到了多個的話,再根據依賴的名稱匹配對應的Bean的名稱
  3. 調用getBean獲得這個須要被注入的Bean
  4. 最後反射調用字段的set方法完成屬性注入

從上面也能夠知道,其實整個屬性注入的邏輯是很簡單的。

若是本文對你有幫助的話,記得點個贊吧!

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

Spring官網閱讀筆記

Spring雜談

JVM系列文章

Spring源碼專題



本文分享自微信公衆號 - 程序員DMZ(programerDmz)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索