Spring源代码解析(二):IOC容器在web容器中的启动

以下引用自博客: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的框架实现作一个详细的分 析。

穿别人的鞋,走自己的路,让别追去吧

Spring源代码解析(二):IOC容器在web容器中的启动

相关文章:

你感兴趣的文章:

标签云: