Spring核心原理之IoC容器初體驗(2)

Tom彈架構發表於2021-12-24

本文節選自《Spring 5核心原理》

1 IoC與DI基本概念

IoC(Inversion of Control,控制反轉)就是把原來程式碼裡需要實現的物件建立、依賴,反轉給容器來幫忙實現。我們需要建立一個容器,同時需要一種描述來讓容器知道要建立的物件與物件的關係。這個描述最具體的表現就是我們所看到的配置檔案。

DI(Dependency Injection,依賴注入)就是指物件被動接受依賴類而不自己主動去找,換句話說,就是指物件不是從容器中查詢它依賴的類,而是在容器例項化物件時主動將它依賴的類注入給它。
我們先從自己設計的視角來考慮。
(1)物件與物件的關係怎麼表示?
可以用XML、properties等語義化配置檔案表示。
(2)描述物件關係的檔案存放在哪裡?
可能是classpath、filesystem或者URL網路資源、servletContext等。
(3)不同的配置檔案對物件的描述不一樣,如標準的、自定義宣告式的,如何統一?
在內部需要有一個統一的關於物件的定義,所有外部的描述都必須轉化成統一的描述定義。
(4)如何對不同的配置檔案進行解析?
需要對不同的配置檔案語法採用不同的解析器。

2 Spring核心容器類圖

2.1. BeanFactory

Spring中Bean的建立是典型的工廠模式,這一系列的Bean工廠,即IoC容器,為開發者管理物件之間的依賴關係提供了很多便利和基礎服務,在Spring中有許多IoC容器的實現供使用者選擇,其相互關係如下圖所示。

file

其中,BeanFactory作為最頂層的一個介面類,定義了IoC容器的基本功能規範,BeanFactory有三個重要的子類:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是從類圖中我們可以發現最終的預設實現類是DefaultListableBeanFactory,它實現了所有的介面。那麼為何要定義這麼多層次的介面呢?查閱這些介面的原始碼和說明發現,每個介面都有它的使用場合,主要是為了區分在Spring內部操作過程中物件的傳遞和轉化,對物件的資料訪問所做的限制。例如,ListableBeanFactory介面表示這些Bean可列表化,而HierarchicalBeanFactory表示這些Bean是有繼承關係的,也就是每個Bean可能有父Bean。AutowireCapableBeanFactory介面定義Bean的自動裝配規則。這三個介面共同定義了Bean的集合、Bean之間的關係及Bean行為。最基本的IoC容器介面是BeanFactory,來看一下它的原始碼:


public interface BeanFactory {
 
   //對FactoryBean的轉義定義,因為如果使用Bean的名字檢索FactoryBean得到的物件是工廠生成的物件
   //如果需要得到工廠本身,需要轉義
   String FACTORY_BEAN_PREFIX = "&";

   //根據Bean的名字,獲取在IoC容器中得到的Bean例項
   Object getBean(String name) throws BeansException;

   //根據Bean的名字和Class型別來得到Bean例項,增加了型別安全驗證機制
   <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
  
   Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

   //提供對Bean的檢索,看看在IoC容器中是否有這個名字的Bean
   boolean containsBean(String name);
  
   //根據Bean的名字得到Bean例項,同時判斷這個Bean是不是單例
   boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
   boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
   boolean isTypeMatch(String name, ResolvableType typeToMatch) throws
 NoSuchBeanDefinitionException;
   boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws
 NoSuchBeanDefinitionException;
  
   //得到Bean例項的Class型別
   @Nullable
   Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    //得到Bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
   String[] getAliases(String name);

}

在BeanFactory裡只對IoC容器的基本行為做了定義,根本不關心你的Bean是如何定義及怎樣載入的。正如我們只關心能從工廠裡得到什麼產品,不關心工廠是怎麼生產這些產品的。
要知道工廠是如何產生物件的,我們需要看具體的IoC容器實現,Spring提供了許多IoC容器實現,比如GenericApplicationContext、ClasspathXmlApplicationContext等。
ApplicationContext是Spring提供的一個高階的IoC容器,它除了能夠提供IoC容器的基本功能,還為使用者提供了以下附加服務。

(1)支援資訊源,可以實現國際化(實現MessageSource介面)。
(2)訪問資源(實現ResourcePatternResolver介面,後面章節會講到)。
(3)支援應用事件(實現ApplicationEventPublisher介面)。

2.2. BeanDefinition

BeanDefinition 用於儲存 Bean 的相關資訊,包括屬性、構造方法引數、依賴的 Bean 名稱及是否單例、延遲載入等,它相當於例項化 Bean 的原材料,Spring 就是根據 BeanDefinition 中的資訊例項化 Bean。,其繼承體系如下圖所示。

file

2.3. BeanDefinitionReader

Bean的解析過程非常複雜,功能被分得很細,因為這裡需要被擴充套件的地方很多,必須保證足夠的靈活性,以應對可能的變化。Bean的解析主要就是對Spring配置檔案的解析。這個解析過程主要通過BeanDefinitionReader來完成,看看Spring中BeanDefinitionReader的類結構圖,如下圖所示。

file

通過前面的分析,我們對Spring框架體系有了一個基本的巨集觀瞭解,希望“小夥伴們”好好理解,最好在腦海中形成畫面,為以後的學習打下良好的基礎。

3 基於Web的IoC容器初體驗

我們還是從大家最熟悉的DispatcherServlet開始,最先想到的應該是DispatcherServlet的init()方法。我們在DispatherServlet中並沒有找到init()方法,經過探索,在其父類HttpServletBean中找到了,程式碼如下:


@Override
public final void init() throws ServletException {
   if (logger.isDebugEnabled()) {
      logger.debug("Initializing servlet '" + getServletName() + "'");
   }

   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
 this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         //定位資源
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         //載入配置資訊
         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, 
getEnvironment()));
         initBeanWrapper(bw);
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
         }
         throw ex;
      }
   }

   initServletBean();

   if (logger.isDebugEnabled()) {
      logger.debug("Servlet '" + getServletName() + "' configured successfully");
   }
}

在init()方法中,真正完成初始化容器動作的程式碼其實在initServletBean()方法中,我們繼續跟進:

@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
   if (this.logger.isInfoEnabled()) {
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }
   catch (RuntimeException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (this.logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed 
            in " + elapsedTime + " ms");
   }
}

在上面的程式碼中終於看到了似曾相識的程式碼initWebApplicationContext(),繼續跟進:

protected WebApplicationContext initWebApplicationContext() {

   //先從ServletContext中獲得父容器WebApplicationContext
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   //宣告子容器
   WebApplicationContext wac = null;

   //建立父、子容器之間的關聯關係
   if (this.webApplicationContext != null) {
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            if (cwac.getParent() == null) {
               cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   //先去ServletContext中查詢Web容器的引用是否存在,並建立好預設的空IoC容器
   if (wac == null) {
      wac = findWebApplicationContext();
   }
   //給上一步建立好的IoC容器賦值
   if (wac == null) {
      wac = createWebApplicationContext(rootContext);
   }
   //觸發onRefresh()方法
   if (!this.refreshEventReceived) {
      onRefresh(wac);
   }

   if (this.publishContext) {
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
      if (this.logger.isDebugEnabled()) {
         this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
               "' as ServletContext attribute with name [" + attrName + "]");
      }
   }

   return wac;
}

@Nullable
protected WebApplicationContext findWebApplicationContext() {
   String attrName = getContextAttribute();
   if (attrName == null) {
      return null;
   }
   WebApplicationContext wac =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
   if (wac == null) {
      throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
   }
   return wac;
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   Class<?> contextClass = getContextClass();
   if (this.logger.isDebugEnabled()) {
      this.logger.debug("Servlet with name '" + getServletName() +
            "' will try to create custom WebApplicationContext context of class '" +
            contextClass.getName() + "'" + ", using parent context [" + parent + "]");
   }
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }

   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(),
 getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   wac.refresh();
}

從上面的程式碼可以看出,在configAndRefreshWebApplicationContext()方法中呼叫了refresh()方法,這是真正啟動IoC容器的入口,後面會詳細介紹。IoC容器初始化以後,呼叫了DispatcherServlet的onRefresh()方法,在onRefresh()方法中又直接呼叫initStrategies()方法初始化Spring MVC的九大元件:

@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

//初始化策略
protected void initStrategies(ApplicationContext context) {
   //多檔案上傳的元件
   initMultipartResolver(context);
   //初始化本地語言環境
   initLocaleResolver(context);
   //初始化模板處理器
   initThemeResolver(context);
   //初始化handlerMapping
   initHandlerMappings(context);
   //初始化引數介面卡
   initHandlerAdapters(context);
   //初始化異常攔截器
   initHandlerExceptionResolvers(context);
   //初始化檢視前處理器
   initRequestToViewNameTranslator(context);
   //初始化檢視轉換器
   initViewResolvers(context);
   //初始化Flashmap管理器
   initFlashMapManager(context);
}

本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。

原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太乾,可以分享轉發給朋友滋潤滋潤!

相關文章