Spring源代碼解析(二):IoC容器在Web容器中的啓動

上面我們分析了IOC容器本身的實現,下面我們看看在典型的web環境中,Spring IOC容器是怎樣被載入和起作用的。
簡單的說,在web容器中,通過ServletContext爲Spring的IOC容器提供宿主環境,對應的建立起一個IOC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業務對象,數據存取對象,資源,事物管理器等各種中間層對象。在這個上下文的基礎上,和web MVC相關還會有一個上下文來保存控制器之類的MVC對象,這樣就構成了一個層次化的上下文結構。在web容器中啓動Spring應用程序就是一個建立這個上下文體系的過程。Spring爲web應用提供了上下文的擴展接口
WebApplicationContext:
Java代碼  收藏代碼
  1. public interface WebApplicationContext extends ApplicationContext {  
  2.     //這裏定義的常量用於在ServletContext中存取根上下文  
  3.     String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";  
  4.     ......  
  5.     //對WebApplicationContext來說,需要得到Web容器的ServletContext  
  6.     ServletContext getServletContext();  
  7. }  

而一般的啓動過程,Spring會使用一個默認的實現,XmlWebApplicationContext - 這個上下文實現作爲在web容器中的根上下文容器被建立起來,具體的建立過程在下面我們會詳細分析。
Java代碼  收藏代碼
  1. public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {  
  2.   
  3.     /** 這是和web部署相關的位置信息,用來作爲默認的根上下文bean定義信息的存放位置*/  
  4.     public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";  
  5.     public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";  
  6.     public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";  
  7.      
  8.     //我們又看到了熟悉的loadBeanDefinition,就像我們前面對IOC容器的分析中一樣,這個加載工程在容器的refresh()的時候啓動。  
  9.     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {  
  10.         //對於XmlWebApplicationContext,當然使用的是XmlBeanDefinitionReader來對bean定義信息來進行解析  
  11.         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  
  12.   
  13.         beanDefinitionReader.setResourceLoader(this);  
  14.         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));  
  15.   
  16.         initBeanDefinitionReader(beanDefinitionReader);  
  17.         loadBeanDefinitions(beanDefinitionReader);  
  18.     }  
  19.   
  20.     protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {  
  21.     }  
  22.     //使用XmlBeanDefinitionReader來讀入bean定義信息  
  23.     protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
  24.         String[] configLocations = getConfigLocations();  
  25.         if (configLocations != null) {  
  26.             for (int i = 0; i < configLocations.length; i++) {  
  27.                 reader.loadBeanDefinitions(configLocations[i]);  
  28.             }  
  29.         }  
  30.     }  
  31.     //這裏取得bean定義信息位置,默認的地方是/WEB-INF/applicationContext.xml  
  32.     protected String[] getDefaultConfigLocations() {  
  33.         if (getNamespace() != null) {  
  34.             return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};  
  35.         }  
  36.         else {  
  37.             return new String[] {DEFAULT_CONFIG_LOCATION};  
  38.         }  
  39.     }  
  40. }  

對於一個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類的靜態方法來得到:
Java代碼  收藏代碼
  1. WebApplicationContext getWebApplicationContext(ServletContext sc)  

以Tomcat作爲Servlet容器爲例,下面是具體的步驟:
1.Tomcat 啓動時需要從web.xml中讀取啓動參數,在web.xml中我們需要對ContextLoaderListener進行配置,對於在web應用啓動入口是在ContextLoaderListener中的初始化部分;從Spring MVC上看,實際上在web容器中維護了一系列的IOC容器,其中在ContextLoader中載入的IOC容器作爲根上下文而存在於 ServletContext中。
Java代碼  收藏代碼
  1. //這裏對根上下文進行初始化。  
  2. public void contextInitialized(ServletContextEvent event) {  
  3.     //這裏創建需要的ContextLoader  
  4.     this.contextLoader = createContextLoader();  
  5.     //這裏使用ContextLoader對根上下文進行載入和初始化  
  6.     this.contextLoader.initWebApplicationContext(event.getServletContext());  
  7. }  

通過ContextLoader建立起根上下文的過程,我們可以在ContextLoader中看到:
Java代碼  收藏代碼
  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext)  
  2.         throws IllegalStateException, BeansException {  
  3.     //這裏先看看是不是已經在ServletContext中存在上下文,如果有說明前面已經被載入過,或者是配置文件有錯誤。  
  4.     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  5.     //直接拋出異常  
  6.     .........  
  7.     }  
  8.     
  9.     ...............  
  10.     try {  
  11.         // 這裏載入根上下文的父上下文  
  12.         ApplicationContext parent = loadParentContext(servletContext);  
  13.   
  14.         //這裏創建根上下文作爲整個應用的上下文同時把它存到ServletContext中去,注意這裏使用的ServletContext的屬性值是  
  15.         //ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以後的應用都是根據這個屬性值來取得根上下文的 - 往往作爲自己上下文的父上下文  
  16.         this.context = createWebApplicationContext(servletContext, parent);  
  17.         servletContext.setAttribute(  
  18.                 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  19.         ..........  
  20.   
  21.         return this.context;  
  22.     }  
  23.        ............  
  24. }  

建立根上下文的父上下文使用的是下面的代碼,取決於在web.xml中定義的參數:locatorFactorySelector,這是一個可選參數:
Java代碼  收藏代碼
  1. protected ApplicationContext loadParentContext(ServletContext servletContext)  
  2.         throws BeansException {  
  3.   
  4.     ApplicationContext parentContext = null;  
  5.   
  6.     String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);  
  7.     String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);  
  8.   
  9.     if (locatorFactorySelector != null) {  
  10.         BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);  
  11.         ........  
  12.         //得到根上下文的父上下文的引用  
  13.         this.parentContextRef = locator.useBeanFactory(parentContextKey);  
  14.         //這裏建立得到根上下文的父上下文  
  15.         parentContext = (ApplicationContext) this.parentContextRef.getFactory();  
  16.     }  
  17.   
  18.     return parentContext;  
  19. }  

得到根上下文的父上下文以後,就是根上下文的創建過程:
Java代碼  收藏代碼
  1. protected WebApplicationContext createWebApplicationContext(  
  2.         ServletContext servletContext, ApplicationContext parent) throws BeansException {  
  3.     //這裏需要確定我們載入的根WebApplication的類型,由在web.xml中配置的contextClass中配置的參數可以決定我們需要載入什麼樣的ApplicationContext,  
  4.     //如果沒有使用默認的。  
  5.     Class contextClass = determineContextClass(servletContext);  
  6.     .........  
  7.     //這裏就是上下文的創建過程  
  8.     ConfigurableWebApplicationContext wac =  
  9.             (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
  10.     //這裏保持對父上下文和ServletContext的引用到根上下文中  
  11.     wac.setParent(parent);  
  12.     wac.setServletContext(servletContext);  
  13.   
  14.     //這裏從web.xml中取得相關的初始化參數  
  15.     String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);  
  16.     if (configLocation != null) {  
  17.         wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,  
  18.                 ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));  
  19.     }  
  20.    //這裏對WebApplicationContext進行初始化,我們又看到了熟悉的refresh調用。  
  21.     wac.refresh();  
  22.     return wac;  
  23. }  

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

3.完成對ContextLoaderListener的初始化以後, Tomcat開始初始化DispatchServlet,- 還記得我們在web.xml中隊載入次序進行了定義。DispatcherServlet會建立自己的ApplicationContext,同時建立這個自己的上下文的時候會從ServletContext中得到根上下文作爲父上下文,然後再對自己的上下文進行初始化,並最後存到 ServletContext中去供以後檢索和使用。
可以從DispatchServlet的父類FrameworkServlet的代碼中看到大致的初始化過程,整個ApplicationContext的創建過程和ContextLoder創建的過程相類似:
Java代碼  收藏代碼
  1. protected final void initServletBean() throws ServletException, BeansException {  
  2.     .........  
  3.     try {  
  4.         //這裏是對上下文的初始化過程。  
  5.         this.webApplicationContext = initWebApplicationContext();  
  6.         //在完成對上下文的初始化過程結束後,根據bean配置信息建立MVC框架的各個主要元素  
  7.         initFrameworkServlet();  
  8.     }  
  9.    ........  
  10. }  

對initWebApplicationContext()調用的代碼如下:
Java代碼  收藏代碼
  1. protected WebApplicationContext initWebApplicationContext() throws BeansException {  
  2.     //這裏調用WebApplicationContextUtils靜態類來得到根上下文  
  3.     WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());  
  4.      
  5.     //創建當前DispatcherServlet的上下文,其上下文種類使用默認的在FrameworkServlet定義好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;  
  6.     WebApplicationContext wac = createWebApplicationContext(parent);  
  7.     ........  
  8.     if (isPublishContext()) {  
  9.         //把當前建立的上下文存到ServletContext中去,注意使用的屬性名是和當前Servlet名相關的。  
  10.         String attrName = getServletContextAttributeName();  
  11.         getServletContext().setAttribute(attrName, wac);  
  12.     }  
  13.     return wac;  
  14. }  

其中我們看到調用了WebApplicationContextUtils的靜態方法得到根ApplicationContext:
Java代碼  收藏代碼
  1.     public static WebApplicationContext getWebApplicationContext(ServletContext sc) {  
  2.         //很簡單,直接從ServletContext中通過屬性名得到根上下文  
  3.         Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
  4.         .......  
  5.         return (WebApplicationContext) attr;  
  6.     }  
  7. 然後創建DispatcherServlet自己的WebApplicationContext:  
  8.     protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)  
  9.             throws BeansException {  
  10.         .......  
  11.         //這裏使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定義好的DEFAULT_CONTEXT_CLASS =                             
  12.         //XmlWebApplicationContext.class;  
  13.         ConfigurableWebApplicationContext wac =  
  14.                 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());  
  15.   
  16.         //這裏配置父上下文,就是在ContextLoader中建立的根上下文  
  17.         wac.setParent(parent);  
  18.   
  19.         //保留ServletContext的引用和相關的配置信息。  
  20.         wac.setServletContext(getServletContext());  
  21.         wac.setServletConfig(getServletConfig());  
  22.         wac.setNamespace(getNamespace());  
  23.   
  24.         //這裏得到ApplicationContext配置文件的位置  
  25.         if (getContextConfigLocation() != null) {  
  26.             wac.setConfigLocations(  
  27.                 StringUtils.tokenizeToStringArray(  
  28.                             getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));  
  29.         }  
  30.          
  31.         //這裏調用ApplicationContext的初始化過程,同樣需要使用refresh()  
  32.         wac.refresh();  
  33.         return wac;  
  34.     }  

4. 然後就是DispatchServlet中對Spring MVC的配置過程,首先對配置文件中的定義元素進行配置 - 請注意這個時候我們的WebApplicationContext已經建立起來了,也意味着DispatcherServlet有自己的定義資源,可以需要從web.xml中讀取bean的配置信息,通常我們會使用單獨的xml文件來配置MVC中各個要素定義,這裏和web容器相關的加載過程實際上已經完成了,下面的處理和普通的Spring應用程序的編寫沒有什麼太大的差別,我們先看看MVC的初始化過程:
Java代碼  收藏代碼
  1. protected void initFrameworkServlet() throws ServletException, BeansException {  
  2.     initMultipartResolver();  
  3.     initLocaleResolver();  
  4.     initThemeResolver();  
  5.     initHandlerMappings();  
  6.     initHandlerAdapters();  
  7.     initHandlerExceptionResolvers();  
  8.     initRequestToViewNameTranslator();  
  9.     initViewResolvers();  
  10. }  

5. 這樣MVC的框架就建立起來了,DispatchServlet對接受到的HTTP Request進行分發處理由doService()完成,具體的MVC處理過程我們在doDispatch()中完成,其中包括使用Command模式建立執行鏈,顯示模型數據等,這些處理我們都可以在DispatcherServlet的代碼中看到:
Java代碼  收藏代碼
  1. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.     ......  
  3.     try {  
  4.         doDispatch(request, response);  
  5.     }  
  6.    .......  
  7. }  

實際的請求分發由doDispatch(request,response)來完成:
Java代碼  收藏代碼
  1. protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.      .......  
  3.      // 這是Spring定義的執行鏈,裏面放了映射關係對應的handler和定義的相關攔截器。  
  4.      HandlerExecutionChain mappedHandler = null;  
  5.      
  6.       ......  
  7.       try {  
  8.           //我們熟悉的ModelAndView在這裏出現了。  
  9.           ModelAndView mv = null;  
  10.           try {  
  11.               processedRequest = checkMultipart(request);  
  12.   
  13.               //這裏更具request中的參數和映射關係定義決定使用的handler  
  14.               mappedHandler = getHandler(processedRequest, false);  
  15.   
  16.               ......  
  17.               //這裏是handler的調用過程,類似於Command模式中的execute.  
  18.               HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  19.               mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  20.   
  21.               .......  
  22.           //這裏將模型數據通過視圖進行展現  
  23.           if (mv != null && !mv.wasCleared()) {  
  24.               render(mv, processedRequest, response);  
  25.           }  
  26.             ........  
  27.   }  

這樣具體的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的框架實現作一個詳細的分析。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章