Spring系列(一):Spring MVC bean 解析、註冊、例項化流程原始碼剖析

七分熟pizza發表於2019-04-18

1.背景

最近在使用Spring MVC過程中遇到了一些問題,網上搜尋不少帖子後雖然找到了答案和解決方法,但這些答案大部分都只是給了結論,並沒有說明具體原因,感覺總是有點不太滿意。

更重要的是這些所謂的結論大多是抄來抄去,基本源自一家,真實性也有待考證。

要成為一名優秀的碼農,不僅能熟練的複製貼上,更要有打破砂鍋問到底的精神,達到知其然也知其所以然的境界。

那作為程式設計師怎麼能知其所以然呢?

答案就是閱讀原始碼!

此處請大家內心默讀三遍。

用過Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring機制就得先從這兩點入手,本文主要通過對IOC部分的機制進行介紹。

2. 實驗環境

在開始閱讀之前,先準備好以下實驗材料。

IDEA 是一個優秀的開發工具,如果還在用Eclipse的建議切換到此工具進行。

IDEA有很多的快捷鍵,在分析過程中建議大家多用Ctrl+Alt+B快捷鍵,可以快速定位到實現函式。

3. Spring Bean 解析註冊

Spring bean的載入主要分為以下6步:

  • (1)讀取XML配置檔案
  • (2)XML檔案解析為document文件
  • (3)解析bean
  • (4)註冊bean
  • (5)例項化bean
  • (6)獲取bean

3.1 讀取XML配置檔案

檢視原始碼第一步是找到程式入口,再以入口為突破口,一步步進行原始碼跟蹤。

Java Web應用中的入口就是web.xml。

在web.xml找到ContextLoaderListener ,此Listener負責初始化Spring IOC。

contextConfigLocation引數設定了bean定義檔案地址。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring.xml</param-value>
</context-param>
複製程式碼

下面是ContextLoaderListener的官方定義:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

Bootstrap listener to start up and shut down Spring's root WebApplicationContext. Simply delegates to ContextLoader as well as to ContextCleanupListener.

docs.spring.io/spring-fram…

翻譯過來ContextLoaderListener作用就是負責啟動和關閉Spring root WebApplicationContext。

具體WebApplicationContext是什麼?開始看原始碼。

package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    //servletContext初始化時候呼叫
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext();
    }
    //servletContext銷燬時候呼叫
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
    }
}
複製程式碼

從原始碼看出此Listener主要有兩個函式,一個負責初始化WebApplicationContext,一個負責銷燬。

繼續看initWebApplicationContext函式。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//初始化Spring容器時如果發現servlet 容器中已存在根Spring容根器則丟擲異常,證明rootWebApplicationContext只能有一個。
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }
   if (this.context == null) {
	//1.建立webApplicationContext例項
        this.context = createWebApplicationContext(servletContext);
   }
   if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
	 //2.配置WebApplicationContext
        configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
    //把生成的webApplicationContext 設定為root webApplicationContext。
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    return this.context; 

}
複製程式碼

在上面的程式碼中主要有兩個功能:

  • (1)建立WebApplicationContext例項。
  • (2)配置生成WebApplicationContext例項。

3.1.1 建立WebApplicationContext例項

進入CreateWebAPPlicationContext函式

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   //得到ContextClass類,預設例項化的是XmlWebApplicationContext類
   Class<?> contextClass = determineContextClass(sc);
   //例項化Context類
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
複製程式碼

進入determineContextClass函式。

protected Class<?> determineContextClass(ServletContext servletContext) {
   // 此處CONTEXT_CLASS_PARAM = "contextClass"String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
   if (contextClassName != null) {
         //若設定了contextClass則使用定義好的ContextClass。
         return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      }
   else {
      //此處獲取的是在Spring原始碼中ContextLoader.properties中配置的org.springframework.web.context.support.XmlWebApplicationContext類。
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
      return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
複製程式碼

3.1.2 配置Web ApplicationContext

進入configureAndReFreshWebApplicaitonContext函式。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  //webapplicationContext設定servletContext.
   wac.setServletContext(sc);
   // 此處CONFIG_LOCATION_PARAM = "contextConfigLocation",即讀即取web.xm中配設定的contextConfigLocation引數值,獲得spring bean的配置檔案.
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
   if (configLocationParam != null) {
      //webApplicationContext設定配置檔案路徑設。
      wac.setConfigLocation(configLocationParam);
   }
   //開始處理bean
   wac.refresh();
}
複製程式碼

3.2 解析XML檔案

上面wac變數宣告為ConfigurableWebApplicationContext型別,ConfigurableWebApplicationContext又繼承了WebApplicationContext。

WebApplication Context有很多實現類。 但從上面determineContextClass得知此處wac實際上是XmlWebApplicationContext類,因此進入XmlWebApplication類檢視其繼承的refresh()方法。

沿方法呼叫棧一層層看下去。

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      //獲取beanFactory
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
     // 例項化所有宣告為非懶載入的單例bean 
      finishBeanFactoryInitialization(beanFactory);
    }
}
複製程式碼

獲取beanFactory。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
     //初始化beanFactory
     refreshBeanFactory();
     return beanFactory;
}
複製程式碼

beanFactory初始化。

@Override
protected final void refreshBeanFactory() throws BeansException {
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      //載入bean定義
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
      this.beanFactory = beanFactory;
      }
}
複製程式碼

載入bean。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   //建立XmlBeanDefinitionReader例項來解析XML配置檔案
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   initBeanDefinitionReader(beanDefinitionReader);
   //解析XML配置檔案中的bean。
   loadBeanDefinitions(beanDefinitionReader);
}
複製程式碼

讀取XML配置檔案。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//此處讀取的就是之前設定好的web.xml中配置檔案地址
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      for (String configLocation : configLocations) {
         //呼叫XmlBeanDefinitionReader讀取XML配置檔案
         reader.loadBeanDefinitions(configLocation);
      }
   }
}
複製程式碼

XmlBeanDefinitionReader讀取XML檔案中的bean定義。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
      Resource resource = resourceLoader.getResource(location);
      //載入bean
      int loadCount = loadBeanDefinitions(resource);
      return loadCount;
   }
}
複製程式碼

繼續檢視loadBeanDefinitons函式呼叫棧,進入到XmlBeanDefinitioReader類的loadBeanDefinitions方法。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
      //獲取檔案流
      InputStream inputStream = encodedResource.getResource().getInputStream();
      InputSource inputSource = new InputSource(inputStream);
     //從檔案流中載入定義好的bean。
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
   }
}
複製程式碼

最終將XML檔案解析成Document文件物件。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      //XML配置檔案解析到Document例項中
      Document doc = doLoadDocument(inputSource, resource);
      //註冊bean
      return registerBeanDefinitions(doc, resource);
   }
複製程式碼

3.3 解析bean

上一步完成了XML檔案的解析工作,接下來將XML中定義的bean註冊到webApplicationContext,繼續跟蹤函式。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //使用documentRedder例項讀取bean定義
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   }
複製程式碼

用BeanDefinitionDocumentReader物件來註冊bean。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   //讀取document元素
   Element root = doc.getDocumentElement();
   //真正開始註冊bean
   doRegisterBeanDefinitions(root);
}
複製程式碼

解析XML文件。

protected void doRegisterBeanDefinitions(Element root) {
    //預處理XML 
   preProcessXml(root);
   //解析註冊bean
   parseBeanDefinitions(root, this.delegate);
   postProcessXml(root);
}
複製程式碼

迴圈解析XML文件中的每個元素。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   //如果該元素屬於預設名稱空間走此邏輯。Spring的預設namespace為:http://www.springframework.org/schema/beans“
   if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element ele = (Element) node;
            //對document中的每個元素都判斷其所屬名稱空間,然後走相應的解析邏輯
            if (delegate.isDefaultNamespace(ele)) {
               parseDefaultElement(ele, delegate);
            }
            else {
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      //如果該元素屬於自定義namespace走此邏輯 ,比如AOP,MVC等。
      delegate.parseCustomElement(root);
   }
}
複製程式碼

下面是預設名稱空間的解析邏輯。

不明白Spring的名稱空間的可以網上查一下,其實類似於package,用來區分變數來源,防止變數重名。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   //解析import元素
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   //解析alias元素
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   //解析bean元素
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   //解析beans元素
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}
複製程式碼

這裡我們就不一一跟蹤,以解析bean元素為例繼續展開。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   //解析bean
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
         // 註冊bean
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
   }
}
複製程式碼

解析bean元素,最後把每個bean解析為一個包含bean所有資訊的BeanDefinitionHolder物件。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
   String id = ele.getAttribute(ID_ATTRIBUTE);
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
複製程式碼

3.4 註冊bean

接下來將解析到的bean註冊到webApplicationContext中。接下繼續跟蹤registerBeanDefinition函式。

public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {
      // 獲取beanname
      String beanName = definitionHolder.getBeanName();
      //註冊bean
      registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
      // 註冊bean的別名
      String[] aliases = definitionHolder.getAliases();
      if (aliases != null) {
          for (String alias : aliases) {
             registry.registerAlias(beanName, alias);
     }
   }
}
複製程式碼

跟蹤registerBeanDefinition函式,此函式將bean資訊儲存到到webApplicationContext的beanDefinitionMap變數中,該變數為map型別,儲存Spring 容器中所有的bean定義。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {
	//把bean資訊儲存到beanDefinitionMap中
        this.beanDefinitionMap.put(beanName, beanDefinition);
	//把beanName 儲存到List 型別的beanDefinitionNames屬性中
    this.beanDefinitionNames.add(beanName);
   }
複製程式碼

3.5 例項化bean

Spring 例項化bean的時機有兩個。

一個是容器啟動時候,另一個是真正呼叫的時候。

如果bean宣告為scope=singleton且lazy-init=false,則容器啟動時候就例項化該bean(Spring 預設就是此行為)。否則在呼叫時候再進行例項化。

相信用過Spring的同學們都知道以上概念,但是為什麼呢?

繼續從原始碼角度進行分析,回到之前XmlWebApplication的refresh()方法。

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      //生成beanFactory,
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // 例項化所有宣告為非懶載入的單例bean 
      finishBeanFactoryInitialization(beanFactory);
 }
複製程式碼

可以看到獲得beanFactory後呼叫了 finishBeanFactoryInitialization()方法,繼續跟蹤此方法。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  // 初始化非懶載入的單例bean   
  beanFactory.preInstantiateSingletons();
}
複製程式碼

預先例項化單例類邏輯。

public void preInstantiateSingletons() throws BeansException {
   // 獲取所有註冊的bean
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
   // 遍歷bean 
   for (String beanName : beanNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      //如果bean是單例且非懶載入,則獲取例項
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            getBean(beanName);
      }
   }
}
複製程式碼

獲取bean。

public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}
複製程式碼

doGetBean中處理的邏輯很多,為了減少干擾,下面只顯示了建立bean的函式呼叫棧。

protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
	//建立bean
       createBean(beanName, mbd, args);
}
複製程式碼

建立bean。

protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    return beanInstance;
}
複製程式碼
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
      throws BeanCreationException {
      // 例項化bean   
      instanceWrapper = createBeanInstance(beanName, mbd, args);
}
複製程式碼
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
   //例項化bean
   return instantiateBean(beanName, mbd);
}
複製程式碼
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
      //呼叫例項化策略進行例項化
      beanInstance = getInstantiationStrategy().instantiate(mbd, beanName,
}
複製程式碼

判斷哪種動態代理方式例項化bean。

public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
   //使用JDK動態代理
   if (bd.getMethodOverrides().isEmpty()) {
      return BeanUtils.instantiateClass(constructorToUse);
   }
   else {
     //使用CGLIB動態代理
     return instantiateWithMethodInjection(bd, beanName, owner);
   }
}
複製程式碼

不管哪種方式最終都是通過反射的形式完成了bean的例項化。

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) 
      ReflectionUtils.makeAccessible(ctor);
      return ctor.newInstance(args);
}
複製程式碼

3.6 獲取bean

我們繼續回到doGetBean函式,分析獲取bean的邏輯。

protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
    //獲取beanName
     final String beanName = transformedBeanName(name);
     Object bean
    // 先檢查該bean是否為單例且容器中是否已經存在例化的單例類
    Object sharedInstance = getSingleton(beanName);
    //如果已存在該bean的單例類
    if (sharedInstance != null && args == null) {
       bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else{
          // 獲取父BeanFactory
          BeanFactory parentBeanFactory = getParentBeanFactory();
          //先判斷該容器中是否註冊了此bean,如果有則在該容器例項化bean,否則再到父容器例項化bean
          if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
               String nameToLookup = originalBeanName(name);
               // 如果父容器有該bean,則呼叫父beanFactory的方法獲得該bean
               return (T) parentBeanFactory.getBean(nameToLookup, args);
           }
            //如果該bean有依賴bean,先實遞迴例化依賴bean。    
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                   registerDependentBean(dep, beanName);
                   getBean(dep);
                }
            }
    	    //如果scope為Singleton執行此邏輯
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                   @Override
                   public Object getObject() throws BeansException {
    		//呼叫建立bean方法
                         return createBean(beanName, mbd, args);
                      }
                    
                   }
                });
             }
             //如果scope為Prototype執行此邏輯,每次獲取時候都例項化一個bean
             else if (mbd.isPrototype()) {
                Object prototypeInstance = null;
                   prototypeInstance = createBean(beanName, mbd, args);
             }
             //如果scope為Request,Session,GolbalSession執行此邏輯
             else {
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                   Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                      @Override
                      public Object getObject() throws BeansException {
                            return createBean(beanName, mbd, args);
                      }
                   });
             }
          }
}
      return (T) bean;
}
複製程式碼

上面方法中首先呼叫getSingleton(beanName)方法來獲取單例bean,如果獲取到則直接返回該bean。方法呼叫棧如下:

public Object getSingleton(String beanName) {
   return getSingleton(beanName, true);
}
複製程式碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//從singletonObjects中獲取bean。
   Object singletonObject = this.singletonObjects.get(beanName);
   return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
複製程式碼

getSingleton方法先從singletonObjects屬性中獲取bean 物件,如果不為空則返回該物件,否則返回null。

那 singletonObjects儲存的是什麼?什麼時候儲存的呢?

回到doGetBean()函式繼續分析。如果singletonObjects沒有該bean的物件,進入到建立bean的邏輯。處理邏輯如下:

//獲取父beanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
//如果該容器中沒有註冊該bean,且父容器不為空,則去父容器中獲取bean後返回
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
      return parentBeanFactory.getBean(nameToLookup, requiredType);
}

複製程式碼

下面是判斷容器中有沒有註冊bean的邏輯,此處beanDefinitionMap相信大家都不陌生,在註冊bean的流程裡已經說過所有的bean資訊都會儲存到該變數中。

public boolean containsBeanDefinition(String beanName) {
   Assert.notNull(beanName, "Bean name must not be null");
   return this.beanDefinitionMap.containsKey(beanName);
}
複製程式碼

如果該容器中已經註冊過bean,繼續往下走。先獲取該bean的依賴bean,如果鑹子依賴bean,則先遞迴獲取相應的依賴bean。

String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
    for (String dep : dependsOn) {
         registerDependentBean(dep, beanName);
         getBean(dep);
    }
}   
複製程式碼

依賴bean建立完成後,接下來就是建立自身bean例項了。

獲取bean例項的處理邏輯有三種,即Singleton、Prototype、其它(request、session、global session),下面一一說明。

3.6.1 Singleton

如果bean是單例模式,執行此邏輯。

if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
          @Override
          public Object getObject() throws BeansException {
    	    //建立bean回撥
            return createBean(beanName, mbd, args); 
       });
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
複製程式碼

獲取單例bean,如果已經有該bean的物件直接返回。如果沒有則建立單例bean物件,並新增到容器的singletonObjects Map中,以後直接從singletonObjects直接獲取bean。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
	  //如果singletonObjects中沒有該bean
      if (singletonObject == null) {
    	//回撥引數傳進來的ObjectFactory的getObject方法,即呼叫createBean方法建立bean例項
            singletonObject = singletonFactory.getObject();
        //置新建立單例bean標誌位為true。
           newSingleton = true;
         if (newSingleton) {
        //如果是新建立bean,註冊新生成bean物件
            addSingleton(beanName, singletonObject);
         }
      }
      //返回獲取的單例bean
      return (singletonObject != NULL_OBJECT ? singletonObject : null);
   }
}
複製程式碼

把新生成的單例bean加入到型別為MAP 的singletonObjects屬性中,這也就是前面singletonObjects()方法中獲取單例bean時從此Map中獲取的原因。

protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
    //把新生成bean物件加入到singletonObjects屬性中。
      this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
 
      this.registeredSingletons.add(beanName);
   }
}
複製程式碼

3.6.2 Prototype

Prototype是每次獲取該bean時候都新建一個bean,因此邏輯比較簡單,直接建立一個bean後返回。

else if (mbd.isPrototype()) {
    Object prototypeInstance = null;
    //建立bean
    prototypeInstance = createBean(beanName, mbd, args);
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
複製程式碼

3.6.3 request、session、global session

else {
    //獲取該bean的scope   
    String scopeName = mbd.getScope();
    //獲取相應scope
    final Scope scope = this.scopes.get(scopeName);
    //獲取相應scope的例項化物件
    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
             @Override
             public Object getObject() throws BeansException {
                   return createBean(beanName, mbd, args);
             }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
   }
複製程式碼

從相應scope獲取物件例項。

public Object get(String name, ObjectFactory<?> objectFactory) {
   RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
   //先從指定scope中獲取bean例項,如果沒有則新建,如果已經有直接返回
   Object scopedObject = attributes.getAttribute(name, getScope());
   if (scopedObject == null) {
      //回撥函式呼叫createBean建立例項
      scopedObject = objectFactory.getObject();
      //建立例項後儲存到相應scope中
      attributes.setAttribute(name, scopedObject, getScope());
      }
   }
   return scopedObject;
}
複製程式碼

判斷scope,獲取例項函式邏輯。

public Object getAttribute(String name, int scope) {
   //scope是request時
   if (scope == SCOPE_REQUEST) {
      //從request中獲取例項
      return this.request.getAttribute(name);
   }
   else {
      PortletSession session = getSession(false);
      if (session != null) {
         //scope是globalSession時,從application中獲取例項
         if (scope == SCOPE_GLOBAL_SESSION) {
            //從globalSession中獲取例項
            Object value = session.getAttribute(name, PortletSession.APPLICATION_SCOPE);
            return value;
         }
         else {
            //從session中獲取例項
            Object value = session.getAttribute(name);
            return value;
         }
      }
      return null;
   }
}
複製程式碼

在相應scope中設定例項函式邏輯。

public void setAttribute(String name, Object value, int scope) {
   if (scope == SCOPE_REQUEST) {
      this.request.setAttribute(name, value);
   }
   else {
      PortletSession session = getSession(true);
      if (scope == SCOPE_GLOBAL_SESSION) {
         session.setAttribute(name, value, PortletSession.APPLICATION_SCOPE);
      }
      else {
         session.setAttribute(name, value);
      }
   }
}
複製程式碼

以上就是Spring bean從無到有的整個邏輯。

4. 小結

從原始碼角度分析 bean的例項化流程到此基本接近尾聲了。

回到開頭的問題,ContextLoaderListener中初始化的WebApplicationContext到底是什麼呢?

通過原始碼的分析我們知道WebApplicationContext負責了bean的建立、儲存、獲取。其實也就是我們平時所說的IOC容器,只不過名字表述不同而已。

5. 尾聲

本文主要是講解了XML配置檔案中bean的解析、註冊、例項化。對於其它名稱空間的解析還沒有講到,後續的文章中會一一介紹。

希望通過本文讓大家在以後使用Spring的過程中有“一切盡在掌控之中”的感覺,而不僅僅是稀裡糊塗的使用。

想要了解更多,關注公眾號:七分熟pizza

在這裡插入圖片描述

相關文章