程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Spring源代碼解析(二):IOC容器在web容器中的啟動

Spring源代碼解析(二):IOC容器在web容器中的啟動

編輯:關於JAVA

以下引用自博客:http://jiwenke-spring.blogspot.com/ 

上面我們分析了IOC容器本身的實現,下面我們看看在典型的web環境中,Spring IOC 容器是怎樣被載入和起作用的。

簡單的說,在web容器中,通過ServletContext為Spring的IOC容器提供宿主環境,對 應的建立起一個IOC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的 對象可以有業務對象,數據存取對象,資源,事物管理器等各種中間層對象。在這個上下 文的基礎上,和web MVC相關還會有一個上下文來保存控制器之類的MVC對象,這樣就構成 了一個層次化的上下文結構。在web容器中啟動Spring應用程序就是一個建立這個上下文 體系的過程。Spring為web應用提供了上下文的擴展接口

WebApplicationContext:

代碼

public interface WebApplicationContext extends ApplicationContext {
   //這裡定義的常量用於在ServletContext中存取根上下文
   String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
   ......
   //對WebApplicationContext來說,需要得到Web容器的ServletContext
   ServletContext getServletContext();
}

而一般的啟動過程,Spring會使用一個默認的實現,XmlWebApplicationContext - 這 個上下文實現作為在web容器中的根上下文容器被建立起來,具體的建立過程在下面我們 會詳細分析。

代碼

public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
   /** 這是和web部署相關的位置信息,用來作為默認的根上下文bean定義信息的存 放位置*/
   public static final String DEFAULT_CONFIG_LOCATION = "/WEB- INF/applicationContext.xml";
   public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB- INF/";
   public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
   //我們又看到了熟悉的loadBeanDefinition,就像我們前面對IOC容器的分析中一 樣,這個加載工程在容器的refresh()的時候啟動。
   protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
     //對於XmlWebApplicationContext,當然使用的是XmlBeanDefinitionReader 來對bean定義信息來進行解析
     XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
     beanDefinitionReader.setResourceLoader(this);
     beanDefinitionReader.setEntityResolver(new ResourceEntityResolver (this));
     initBeanDefinitionReader(beanDefinitionReader);
     loadBeanDefinitions(beanDefinitionReader);
   }
   protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
   }
   //使用XmlBeanDefinitionReader來讀入bean定義信息
   protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
     String[] configLocations = getConfigLocations();
     if (configLocations != null) {
       for (int i = 0; i < configLocations.length; i++) {
         reader.loadBeanDefinitions(configLocations[i]);
       }
     }
   }
   //這裡取得bean定義信息位置,默認的地方是/WEB- INF/applicationContext.xml
   protected String[] getDefaultConfigLocations() {
     if (getNamespace() != null) {
       return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
     }
     else {
       return new String[] {DEFAULT_CONFIG_LOCATION};
     }
   }
}

對於一個Spring激活的web應用程序,可以通過使用Spring代碼聲明式的指定在web應 用程序啟動時載入應用程序上下文(WebApplicationContext),Spring的ContextLoader是 提供這樣性能的類,我們可以使用 ContextLoaderServlet或者ContextLoaderListener的 啟動時載入的Servlet來實例化Spring IOC容器 - 為什麼會有兩個不同的類來裝載它呢, 這是因為它們的使用需要區別不同的Servlet容器支持的Serlvet版本。但不管是 ContextLoaderSevlet還是 ContextLoaderListener都使用ContextLoader來完成實際的 WebApplicationContext的初始化工作。這個ContextLoder就像是Spring Web應用程序在 Web容器中的加載器booter。當然這些Servlet的具體使用我們都要借助web容器中的部署 描述符來進行相關的定義。

下面我們使用ContextLoaderListener作為載入器作一個詳細的分析,這個Servlet的 監聽器是根上下文被載入的地方,也是整個 Spring web應用加載上下文的第一個地方; 從加載過程我們可以看到,首先從Servlet事件中得到ServletContext,然後可以讀到配 置好的在web.xml的中的各個屬性值,然後ContextLoder實例化WebApplicationContext並 完成其載入和初始化作為根上下文。當這個根上下文被載入後,它被綁定到web應用程序 的ServletContext上。任何需要訪問該ApplicationContext的應用程序代碼都可以從 WebApplicationContextUtils類的靜態方法來得到:

代碼

WebApplicationContext getWebApplicationContext(ServletContext sc)

以Tomcat作為Servlet容器為例,下面是具體的步驟:

1.Tomcat 啟動時需要從web.xml中讀取啟動參數,在web.xml中我們需要對 ContextLoaderListener進行配置,對於在web應用啟動入口是在ContextLoaderListener 中的初始化部分;從Spring MVC上看,實際上在web容器中維護了一系列的IOC容器,其中 在ContextLoader中載入的IOC容器作為根上下文而存在於 ServletContext中。

代碼

//這裡對根上下文進行初始化。
public void contextInitialized(ServletContextEvent event) {
   //這裡創建需要的ContextLoader
   this.contextLoader = createContextLoader();
   //這裡使用ContextLoader對根上下文進行載入和初始化
   this.contextLoader.initWebApplicationContext(event.getServletContext ());
}

通過ContextLoader建立起根上下文的過程,我們可以在ContextLoader中看到:

代碼

public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
     throws IllegalStateException, BeansException {
   //這裡先看看是不是已經在ServletContext中存在上下文,如果有說明前面已經被 載入過,或者是配置文件有錯誤。
   if (servletContext.getAttribute (WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
   //直接拋出異常
   .........
   }
   ...............
   try {
     // 這裡載入根上下文的父上下文
     ApplicationContext parent = loadParentContext(servletContext);
     //這裡創建根上下文作為整個應用的上下文同時把它存到ServletContext中 去,注意這裡使用的ServletContext的屬性值是
     //ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以後的應用都是根據這個屬 性值來取得根上下文的 - 往往作為自己上下文的父上下文
     this.context = createWebApplicationContext(servletContext, parent);
     servletContext.setAttribute(
         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
     ..........
     return this.context;
   }
    ............
}

建立根上下文的父上下文使用的是下面的代碼,取決於在web.xml中定義的參數: locatorFactorySelector,這是一個可選參數:

代碼

protected ApplicationContext loadParentContext(ServletContext servletContext)
     throws BeansException {
   ApplicationContext parentContext = null;
   String locatorFactorySelector = servletContext.getInitParameter (LOCATOR_FACTORY_SELECTOR_PARAM);
   String parentContextKey = servletContext.getInitParameter (LOCATOR_FACTORY_KEY_PARAM);
   if (locatorFactorySelector != null) {
     BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
     ........
     //得到根上下文的父上下文的引用
     this.parentContextRef = locator.useBeanFactory (parentContextKey);
     //這裡建立得到根上下文的父上下文
     parentContext = (ApplicationContext) this.parentContextRef.getFactory();
   }
   return parentContext;
}

得到根上下文的父上下文以後,就是根上下文的創建過程:

代碼

protected WebApplicationContext createWebApplicationContext(
     ServletContext servletContext, ApplicationContext parent) throws BeansException {
   //這裡需要確定我們載入的根WebApplication的類型,由在web.xml中配置的 contextClass中配置的參數可以決定我們需要載入什麼樣的ApplicationContext,
   //如果沒有使用默認的。
   Class contextClass = determineContextClass(servletContext);
   .........
   //這裡就是上下文的創建過程
   ConfigurableWebApplicationContext wac =
       (ConfigurableWebApplicationContext) BeanUtils.instantiateClass (contextClass);
   //這裡保持對父上下文和ServletContext的引用到根上下文中
   wac.setParent(parent);
   wac.setServletContext(servletContext);
   //這裡從web.xml中取得相關的初始化參數
   String configLocation = servletContext.getInitParameter (CONFIG_LOCATION_PARAM);
   if (configLocation != null) {
     wac.setConfigLocations(StringUtils.tokenizeToStringArray (configLocation,
         ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
   }
  //這裡對WebApplicationContext進行初始化,我們又看到了熟悉的refresh調用。
   wac.refresh();
   return wac;
}

初始化根ApplicationContext後將其存儲到SevletContext中去以後,這樣就建立了一 個全局的關於整個應用的上下文。這個根上下文會被以後的DispatcherServlet初始化自 己的時候作為自己ApplicationContext的父上下文。這個在對 DispatcherServlet做分析 的時候我們可以看看到。

3.完成對ContextLoaderListener的初始化以後, Tomcat開始初始化DispatchServlet ,- 還記得我們在web.xml中隊載入次序進行了定義。DispatcherServlet會建立自己的 ApplicationContext,同時建立這個自己的上下文的時候會從ServletContext中得到根上 下文作為父上下文,然後再對自己的上下文進行初始化,並最後存到 ServletContext中去 供以後檢索和使用。

可以從DispatchServlet的父類FrameworkServlet的代碼中看到大致的初始化過程,整 個ApplicationContext的創建過程和ContextLoder創建的過程相類似:

代碼

protected final void initServletBean() throws ServletException, BeansException {
   .........
   try {
     //這裡是對上下文的初始化過程。
     this.webApplicationContext = initWebApplicationContext();
     //在完成對上下文的初始化過程結束後,根據bean配置信息建立MVC框架的各 個主要元素
     initFrameworkServlet();
   }
  ........
}

對initWebApplicationContext()調用的代碼如下:

代碼

protected WebApplicationContext initWebApplicationContext() throws BeansException {
   //這裡調用WebApplicationContextUtils靜態類來得到根上下文
   WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   //創建當前DispatcherServlet的上下文,其上下文種類使用默認的在 FrameworkServlet定義好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
   WebApplicationContext wac = createWebApplicationContext(parent);
   ........
   if (isPublishContext()) {
     //把當前建立的上下文存到ServletContext中去,注意使用的屬性名是和當 前Servlet名相關的。
     String attrName = getServletContextAttributeName();
     getServletContext().setAttribute(attrName, wac);
   }
   return wac;
}

其中我們看到調用了WebApplicationContextUtils的靜態方法得到根 ApplicationContext:

代碼

public static WebApplicationContext getWebApplicationContext (ServletContext sc) {
     //很簡單,直接從ServletContext中通過屬性名得到根上下文
     Object attr = sc.getAttribute (WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
     .......
     return (WebApplicationContext) attr;
}

然後創建DispatcherServlet自己的WebApplicationContext:

protected WebApplicationContext createWebApplicationContext (WebApplicationContext parent)
       throws BeansException {
     .......
     //這裡使用了BeanUtils直接得到WebApplicationContext,ContextClass是前 面定義好的DEFAULT_CONTEXT_CLASS =
     //XmlWebApplicationContext.class;
     ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
     //這裡配置父上下文,就是在ContextLoader中建立的根上下文
     wac.setParent(parent);
     //保留ServletContext的引用和相關的配置信息。
     wac.setServletContext(getServletContext());
     wac.setServletConfig(getServletConfig());
     wac.setNamespace(getNamespace());
     //這裡得到ApplicationContext配置文件的位置
     if (getContextConfigLocation() != null) {
       wac.setConfigLocations(
         StringUtils.tokenizeToStringArray(
               getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
     }
     //這裡調用ApplicationContext的初始化過程,同樣需要使用refresh()
     wac.refresh();
     return wac;
   }

4. 然後就是DispatchServlet中對Spring MVC的配置過程,首先對配置文件中的定義 元素進行配置 - 請注意這個時候我們的WebApplicationContext已經建立起來了,也意味 著DispatcherServlet有自己的定義資源,可以需要從web.xml中讀取bean的配置信息,通 常我們會使用單獨的xml文件來配置MVC中各個要素定義,這裡和web容器相關的加載過程 實際上已經完成了,下面的處理和普通的Spring應用程序的編寫沒有什麼太大的差別,我 們先看看MVC的初始化過程:

代碼

protected void initFrameworkServlet() throws ServletException, BeansException {
   initMultipartResolver();
   initLocaleResolver();
   initThemeResolver();
   initHandlerMappings();
   initHandlerAdapters();
   initHandlerExceptionResolvers();
   initRequestToViewNameTranslator();
   initViewResolvers();
}

5. 這樣MVC的框架就建立起來了,DispatchServlet對接受到的HTTP Request進行分發 處理由doService()完成,具體的MVC處理過程我們在doDispatch()中完成,其中包括使用 Command模式建立執行鏈,顯示模型數據等,這些處理我們都可以在DispatcherServlet的 代碼中看到:

代碼

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   ......
   try {
     doDispatch(request, response);
   }
  .......
}

實際的請求分發由doDispatch(request,response)來完成:

代碼

protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
   .......
   // 這是Spring定義的執行鏈,裡面放了映射關系對應的handler和定義的相關攔 截器。
   HandlerExecutionChain mappedHandler = null;
    ......
    try {
      //我們熟悉的ModelAndView在這裡出現了。
      ModelAndView mv = null;
      try {
        processedRequest = checkMultipart(request);
        //這裡更具request中的參數和映射關系定義決定使用的handler
        mappedHandler = getHandler(processedRequest, false);
        ......
        //這裡是handler的調用過程,類似於Command模式中的execute.
        HandlerAdapter ha = getHandlerAdapter (mappedHandler.getHandler());
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        .......
      //這裡將模型數據通過視圖進行展現
      if (mv != null && !mv.wasCleared()) {
        render(mv, processedRequest, response);
      }
       ........
  }

這樣具體的MVC模型的實現就由bean配置文件裡定義好的view resolver,handler這些 類來實現用戶代碼的功能。

總結上面的過程,我們看到在web容器中,ServletContext可以持有一系列的web上下 文,而在整個web上下文中存在一個根上下文來作為其它 Servlet上下文的父上下文。這 個根上下文是由ContextLoader載入並進行初始化的,對於我們的web應用, DispatcherSerlvet載入並初始化自己的上下文,這個上下文的父上下文是根上下文,並且 我們也能從ServletContext中根據 Servlet的名字來檢索到我們需要的對應於這個 Servlet的上下文,但是根上下文的名字是由Spring唯一確定的。這個 DispactcherServlet建立的上下文就是我們開發Spring MVC應用的IOC容器。

具體的web請求處理在上下文體系建立完成以後由DispactcherServlet來完成,上面對 MVC的運作做了一個大致的描述,下面我們會具體就SpringMVC的框架實現作一個詳細的分 析。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved