读书人

Spring源代码分析之(2):IOC容器在web

发布时间: 2012-10-08 19:54:56 作者: rapoo

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:

代码?
  1. public?interface?WebApplicationContext?extends?ApplicationContext?{ ?? ????//这里定义的常量用于在ServletContext中存取根上下文 ??
  2. ????String?ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE?=?WebApplicationContext.class.getName()?+?".ROOT"; ?? ????...... ??
  3. ????//对WebApplicationContext来说,需要得到Web容器的ServletContext ?? ????ServletContext?getServletContext(); ??
  4. } ??

<script type="text/javascript">render_code();</script>
而一般的启动过程,Spring会使用一个默认的实现,XmlWebApplicationContext - 这个上下文实现作为在web容器中的根上下文容器被建立起来,具体的建立过程在下面我们会详细分析。

代码?
  1. public?class?XmlWebApplicationContext?extends?AbstractRefreshableWebApplicationContext?{ ?? ??
  2. ????/**?这是和web部署相关的位置信息,用来作为默认的根上下文bean定义信息的存放位置*/?? ????public?static?final?String?DEFAULT_CONFIG_LOCATION?=?"/WEB-INF/applicationContext.xml"; ??
  3. ????public?static?final?String?DEFAULT_CONFIG_LOCATION_PREFIX?=?"/WEB-INF/"; ?? ????public?static?final?String?DEFAULT_CONFIG_LOCATION_SUFFIX?=?".xml"; ??
  4. ??? ?? ????//我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容器的分析中一样,这个加载工程在容器的refresh()的时候启动。 ??
  5. ????protected?void?loadBeanDefinitions(DefaultListableBeanFactory?beanFactory)?throws?IOException?{ ?? ????????//对于XmlWebApplicationContext,当然使用的是XmlBeanDefinitionReader来对bean定义信息来进行解析 ??
  6. ????????XmlBeanDefinitionReader?beanDefinitionReader?=?new?XmlBeanDefinitionReader(beanFactory); ?? ??
  7. ????????beanDefinitionReader.setResourceLoader(this); ?? ????????beanDefinitionReader.setEntityResolver(new?ResourceEntityResolver(this)); ??
  8. ?? ????????initBeanDefinitionReader(beanDefinitionReader); ??
  9. ????????loadBeanDefinitions(beanDefinitionReader); ?? ????} ??
  10. ?? ????protected?void?initBeanDefinitionReader(XmlBeanDefinitionReader?beanDefinitionReader)?{ ??
  11. ????} ?? ????//使用XmlBeanDefinitionReader来读入bean定义信息 ??
  12. ????protected?void?loadBeanDefinitions(XmlBeanDefinitionReader?reader)?throws?BeansException,?IOException?{ ?? ????????String[]?configLocations?=?getConfigLocations(); ??
  13. ????????if?(configLocations?!=?null)?{ ?? ????????????for?(int?i?=?0;?i?<?configLocations.length;?i++)?{ ??
  14. ????????????????reader.loadBeanDefinitions(configLocations[i]); ?? ????????????} ??
  15. ????????} ?? ????} ??
  16. ????//这里取得bean定义信息位置,默认的地方是/WEB-INF/applicationContext.xml ?? ????protected?String[]?getDefaultConfigLocations()?{ ??
  17. ????????if?(getNamespace()?!=?null)?{ ?? ????????????return?new?String[]?{DEFAULT_CONFIG_LOCATION_PREFIX?+?getNamespace()?+?DEFAULT_CONFIG_LOCATION_SUFFIX}; ??
  18. ????????} ?? ????????else?{ ??
  19. ????????????return?new?String[]?{DEFAULT_CONFIG_LOCATION}; ?? ????????} ??
  20. ????} ?? } ??

<script type="text/javascript">render_code();</script>
对于一个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类的静态方法来得到:

代码
  1. WebApplicationContext?getWebApplicationContext(ServletContext?sc) ??

<script type="text/javascript">render_code();</script>
以Tomcat作为Servlet容器为例,下面是具体的步骤:
1.Tomcat 启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分;从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于 ServletContext中。

代码?
  1. //这里对根上下文进行初始化。 ?? public?void?contextInitialized(ServletContextEvent?event)?{ ??
  2. ????//这里创建需要的ContextLoader ?? ????this.contextLoader?=?createContextLoader(); ??
  3. ????//这里使用ContextLoader对根上下文进行载入和初始化 ?? ????this.contextLoader.initWebApplicationContext(event.getServletContext()); ??
  4. } ??

<script type="text/javascript">render_code();</script>
通过ContextLoader建立起根上下文的过程,我们可以在ContextLoader中看到:

代码?
  1. public?WebApplicationContext?initWebApplicationContext(ServletContext?servletContext) ?? ????????throws?IllegalStateException,?BeansException?{ ??
  2. ????//这里先看看是不是已经在ServletContext中存在上下文,如果有说明前面已经被载入过,或者是配置文件有错误。 ?? ????if?(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)?!=?null)?{ ??
  3. ????//直接抛出异常 ?? ????......... ??
  4. ????} ?? ?? ??
  5. ????............... ?? ????try?{ ??
  6. ????????//?这里载入根上下文的父上下文 ?? ????????ApplicationContext?parent?=?loadParentContext(servletContext); ??
  7. ?? ????????//这里创建根上下文作为整个应用的上下文同时把它存到ServletContext中去,注意这里使用的ServletContext的属性值是 ??
  8. ????????//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的应用都是根据这个属性值来取得根上下文的?-?往往作为自己上下文的父上下文 ?? ????????this.context?=?createWebApplicationContext(servletContext,?parent); ??
  9. ????????servletContext.setAttribute( ?? ????????????????WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,?this.context); ??
  10. ????????.......... ?? ??
  11. ????????return?this.context; ?? ????} ??
  12. ???????............ ?? } ??

<script type="text/javascript">render_code();</script>
建立根上下文的父上下文使用的是下面的代码,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数:

代码?
  1. protected?ApplicationContext?loadParentContext(ServletContext?servletContext) ?? ????????throws?BeansException?{ ??
  2. ?? ????ApplicationContext?parentContext?=?null; ??
  3. ?? ????String?locatorFactorySelector?=?servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); ??
  4. ????String?parentContextKey?=?servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); ?? ??
  5. ????if?(locatorFactorySelector?!=?null)?{ ?? ????????BeanFactoryLocator?locator?=?ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); ??
  6. ????????........ ?? ????????//得到根上下文的父上下文的引用 ??
  7. ????????this.parentContextRef?=?locator.useBeanFactory(parentContextKey); ?? ????????//这里建立得到根上下文的父上下文 ??
  8. ????????parentContext?=?(ApplicationContext)?this.parentContextRef.getFactory(); ?? ????} ??
  9. ?? ????return?parentContext; ??
  10. } ??

<script type="text/javascript">render_code();</script>
得到根上下文的父上下文以后,就是根上下文的创建过程:

代码?
  1. protected?WebApplicationContext?createWebApplicationContext( ?? ????????ServletContext?servletContext,?ApplicationContext?parent)?throws?BeansException?{ ??
  2. ????//这里需要确定我们载入的根WebApplication的类型,由在web.xml中配置的contextClass中配置的参数可以决定我们需要载入什么样的ApplicationContext, ?? ????//如果没有使用默认的。 ??
  3. ????Class?contextClass?=?determineContextClass(servletContext); ?? ????......... ??
  4. ????//这里就是上下文的创建过程 ?? ????ConfigurableWebApplicationContext?wac?= ??
  5. ????????????(ConfigurableWebApplicationContext)?BeanUtils.instantiateClass(contextClass); ?? ????//这里保持对父上下文和ServletContext的引用到根上下文中 ??
  6. ????wac.setParent(parent); ?? ????wac.setServletContext(servletContext); ??
  7. ?? ????//这里从web.xml中取得相关的初始化参数 ??
  8. ????String?configLocation?=?servletContext.getInitParameter(CONFIG_LOCATION_PARAM); ?? ????if?(configLocation?!=?null)?{ ??
  9. ????????wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation, ?? ????????????????ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); ??
  10. ????} ?? ???//这里对WebApplicationContext进行初始化,我们又看到了熟悉的refresh调用。 ??
  11. ????wac.refresh(); ?? ????return?wac; ??
  12. } ??

<script type="text/javascript">render_code();</script>
初始化根ApplicationContext后将其存储到SevletContext中去以后,这样就建立了一个全局的关于整个应用的上下文。这个根上下文会被以后的DispatcherServlet初始化自己的时候作为自己ApplicationContext的父上下文。这个在对 DispatcherServlet做分析的时候我们可以看看到。

?

3.完成对ContextLoaderListener的初始化以后, Tomcat开始初始化DispatchServlet,- 还记得我们在web.xml中队载入次序进行了定义。DispatcherServlet会建立自己的ApplicationContext,同时建立这个自己的上下文的时候会从ServletContext中得到根上下文作为父上下文,然后再对自己的上下文进行初始化,并最后存到 ServletContext中去供以后检索和使用。
可以从DispatchServlet的父类FrameworkServlet的代码中看到大致的初始化过程,整个ApplicationContext的创建过程和ContextLoder创建的过程相类似:

代码?
  1. protected?final?void?initServletBean()?throws?ServletException,?BeansException?{ ?? ????......... ??
  2. ????try?{ ?? ????????//这里是对上下文的初始化过程。 ??
  3. ????????this.webApplicationContext?=?initWebApplicationContext(); ?? ????????//在完成对上下文的初始化过程结束后,根据bean配置信息建立MVC框架的各个主要元素 ??
  4. ????????initFrameworkServlet(); ?? ????} ??
  5. ???........ ?? } ??

<script type="text/javascript">render_code();</script>
对initWebApplicationContext()调用的代码如下:

代码?
  1. protected?WebApplicationContext?initWebApplicationContext()?throws?BeansException?{ ?? ????//这里调用WebApplicationContextUtils静态类来得到根上下文 ??
  2. ????WebApplicationContext?parent?=?WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ?? ??? ??
  3. ????//创建当前DispatcherServlet的上下文,其上下文种类使用默认的在FrameworkServlet定义好的:DEFAULT_CONTEXT_CLASS?=?XmlWebApplicationContext.class; ?? ????WebApplicationContext?wac?=?createWebApplicationContext(parent); ??
  4. ????........ ?? ????if?(isPublishContext())?{ ??
  5. ????????//把当前建立的上下文存到ServletContext中去,注意使用的属性名是和当前Servlet名相关的。 ?? ????????String?attrName?=?getServletContextAttributeName(); ??
  6. ????????getServletContext().setAttribute(attrName,?wac); ?? ????} ??
  7. ????return?wac; ?? } ??

<script type="text/javascript">render_code();</script>
其中我们看到调用了WebApplicationContextUtils的静态方法得到根ApplicationContext:

代码?
  1. ????public?static?WebApplicationContext?getWebApplicationContext(ServletContext?sc)?{ ?? ????????//很简单,直接从ServletContext中通过属性名得到根上下文 ??
  2. ????????Object?attr?=?sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); ?? ????????....... ??
  3. ????????return?(WebApplicationContext)?attr; ?? ????} ??
  4. 然后创—ispatcherServlet自己的WebApplicationContext: ?? ????protected?WebApplicationContext?createWebApplicationContext(WebApplicationContext?parent) ??
  5. ????????????throws?BeansException?{ ?? ????????....... ??
  6. ????????//这里使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定义好的DEFAULT_CONTEXT_CLASS?=??????????????????????????? ?? ????????//XmlWebApplicationContext.class; ??
  7. ????????ConfigurableWebApplicationContext?wac?= ?? ????????????????(ConfigurableWebApplicationContext)?BeanUtils.instantiateClass(getContextClass()); ??
  8. ?? ????????//这里配置父上下文,就是在ContextLoader中建立的根上下文 ??
  9. ????????wac.setParent(parent); ?? ??
  10. ????????//保留ServletContext的引用和相关的配置信息。 ?? ????????wac.setServletContext(getServletContext()); ??
  11. ????????wac.setServletConfig(getServletConfig()); ?? ????????wac.setNamespace(getNamespace()); ??
  12. ?? ????????//这里得到ApplicationContext配置文件的位置 ??
  13. ????????if?(getContextConfigLocation()?!=?null)?{ ?? ????????????wac.setConfigLocations( ??
  14. ????????????????StringUtils.tokenizeToStringArray( ?? ????????????????????????????getContextConfigLocation(),?ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); ??
  15. ????????} ?? ??????? ??
  16. ????????//这里调用ApplicationContext的初始化过程,同样需要使用refresh() ?? ????????wac.refresh(); ??
  17. ????????return?wac; ?? ????} ??

<script type="text/javascript">render_code();</script>
4. 然后就是DispatchServlet中对Spring MVC的配置过程,首先对配置文件中的定义元素进行配置 - 请注意这个时候我们的WebApplicationContext已经建立起来了,也意味着DispatcherServlet有自己的定义资源,可以需要从web.xml中读取bean的配置信息,通常我们会使用单独的xml文件来配置MVC中各个要素定义,这里和web容器相关的加载过程实际上已经完成了,下面的处理和普通的Spring应用程序的编写没有什么太大的差别,我们先看看MVC的初始化过程:

代码?
  1. protected?void?initFrameworkServlet()?throws?ServletException,?BeansException?{ ?? ????initMultipartResolver(); ??
  2. ????initLocaleResolver(); ?? ????initThemeResolver(); ??
  3. ????initHandlerMappings(); ?? ????initHandlerAdapters(); ??
  4. ????initHandlerExceptionResolvers(); ?? ????initRequestToViewNameTranslator(); ??
  5. ????initViewResolvers(); ?? } ??

<script type="text/javascript">render_code();</script>
5. 这样MVC的框架就建立起来了,DispatchServlet对接受到的HTTP Request进行分发处理由doService()完成,具体的MVC处理过程我们在doDispatch()中完成,其中包括使用Command模式建立执行链,显示模型数据等,这些处理我们都可以在DispatcherServlet的代码中看到:

代码?
  1. protected?void?doService(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{ ?? ????...... ??
  2. ????try?{ ?? ????????doDispatch(request,?response); ??
  3. ????} ?? ???....... ??
  4. } ??

<script type="text/javascript">render_code();</script>
实际的请求分发由doDispatch(request,response)来完成:

代码?
  1. protected?void?doDispatch(final?HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?{ ?? ?????....... ??
  2. ?????//?这是Spring定义的执行链,里面放了映射关系对应的handler和定义的相关拦截器。 ?? ?????HandlerExecutionChain?mappedHandler?=?null; ??
  3. ??? ?? ??????...... ??
  4. ??????try?{ ?? ??????????//我们熟悉的ModelAndView在这里出现了。 ??
  5. ??????????ModelAndView?mv?=?null; ?? ??????????try?{ ??
  6. ??????????????processedRequest?=?checkMultipart(request); ?? ??
  7. ??????????????//这里更具request中的参数和映射关系定义决定使用的handler ?? ??????????????mappedHandler?=?getHandler(processedRequest,?false); ??
  8. ?? ??????????????...... ??
  9. ??????????????//这里是handler的调用过程,类似于Command模式中的execute. ?? ??????????????HandlerAdapter?ha?=?getHandlerAdapter(mappedHandler.getHandler()); ??
  10. ??????????????mv?=?ha.handle(processedRequest,?response,?mappedHandler.getHandler()); ?? ??
  11. ??????????????....... ?? ??????????//这里将模型数据通过视图进行展现 ??
  12. ??????????if?(mv?!=?null?&&?!mv.wasCleared())?{ ?? ??????????????render(mv,?processedRequest,?response); ??
  13. ??????????} ?? ????????????........ ??
  14. ??} ??

<script type="text/javascript">render_code();</script>
这样具体的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的框架实现作一个详细的分析。?

读书人网 >Web前端

热点推荐