您的位置 首页 >  博文

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:

1public 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 容器中的根上下文容器被建立起来,具体的建立过程在下面我们会详细分析。

 1public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext 
2    /** 这是和 web 部署相关的位置信息,用来作为默认的根上下文 bean 定义信息的存放位置*/ 
3    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"
4    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"
5    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"
6
7    //我们又看到了熟悉的 loadBeanDefinition,就像我们前面对 IOC 容器的分析中一样,这个加载工程在容器的 refresh()的时候启动。 
8    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOExceptio
9
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
20protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {} 
21//使用 XmlBeanDefinitionReader 来读入 bean 定义信息 
22protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException 
23    String[] configLocations = getConfigLocations(); 
24    if (configLocations != null) { 
25        for (int i = 0; i < configLocations.length; i++) { 
26            reader.loadBeanDefinitions(configLocations[i]); 
27        } 
28    } 
29
30//这里取得 bean 定义信息位置,默认的地方是/WEB-INF/applicationContext.xml 
31protected String[] getDefaultConfigLocations() { 
32    if (getNamespace() != null) { 
33        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; 
34    }else { 
35        return new String[] {DEFAULT_CONFIG_LOCATION}; 
36    } 
37}  

对于一个 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 的监听器是根上下文被载入的地方,也是整个 Springweb 应用加载上下文的第一个地方;从加载过程我们可以看到,首先从 Servlet 事件中得到 ServletContext,然后可以读到配置好的在web.xml 的中的各个属性值,然后 ContextLoder 实例化WebApplicationContext 并完成其载入和初始化作为根上下文。当这个根上下文被载入后,它被绑定到 web 应用程序的 ServletContext 上。任何需要访问该 ApplicationContext 的应用程序代码都可以从WebApplicationContextUtils 类的静态方法来得到:

1WebApplicationContext getWebApplicationContext(ServletContext sc) 
2

以 Tomcat 作为 Servlet 容器为例,下面是具体的步骤:

  1. Tomcat 启动时需要从 web.xml 中读取启动参数,在 web.xml 中我们需要对 ContextLoaderListener 进行配置,对于在 web 应用启动入口是在 ContextLoaderListener 中的初始化部分;从 Spring MVC 上看,实际上在 web 容器中维护了一系列的 IOC 容器,其中在ContextLoader 中载入的 IOC 容器作为根上下文而存在于 ServletContext 中。

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

  1. 通过 ContextLoader 建立起根上下文的过程,我们可以在 ContextLoader 中看到:

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

建立根上下文的父上下文使用的是下面的代码,取决于在 web.xml 中定义的参数:locatorFactorySelector,这是一个可选参数:

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

得到根上下文的父上下文以后,就是根上下文的创建过程:

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

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


  1. 完成对 ContextLoaderListener 的初始化以后, Tomcat 开始初始化 DispatchServlet,- 还记得我们在 web.xml 中队载入次序进行了定义。DispatcherServlet 会建立自己的 ApplicationContext,同时建立这个自己的上下文的时候会从 ServletContext 中得到根上下文作为父上下文,然后再对自己的上下文进行初始化,并最后存到 ServletContext 中去供以后检索和使用。

可以从 DispatchServlet 的父类 FrameworkServlet 的代码中看到大致的初始化过程,整个 ApplicationContext 的创建过程和 ContextLoder创建的过程相类似:

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

对 initWebApplicationContext()调用的代码如下:

 1protected 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:

 1public 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: 
8protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) throws BeansException 
9    ...
10    //这里使用了 BeanUtils 直接得到 WebApplicationContext,ContextClass 是前面定义好的 DEFAULT_CONTEXT_CLASS = 
11    //XmlWebApplicationContext.class; 
12    ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass()); 
13
14    //这里配置父上下文,就是在 ContextLoader 中建立的根上下文 
15    wac.setParent(parent); 
16
17    //保留 ServletContext 的引用和相关的配置信息。 
18    wac.setServletContext(getServletContext()); 
19    wac.setServletConfig(getServletConfig()); 
20    wac.setNamespace(getNamespace()); 
21
22    //这里得到 ApplicationContext 配置文件的位置 
23    if (getContextConfigLocation() != null) { 
24        wac.setConfigLocations( 
25        StringUtils.tokenizeToStringArray(getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); 
26    } 
27
28    //这里调用 ApplicationContext 的初始化过程,同样需要使用 refresh() 
29    wac.refresh(); 
30    return wac; 
31

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

 1protected void initFrameworkServlet() throws ServletException, BeansException 
2    initMultipartResolver(); 
3    initLocaleResolver(); 
4    initThemeResolver(); 
5    initHandlerMappings(); 
6    initHandlerAdapters(); 
7    initHandlerExceptionResolvers(); 
8    initRequestToViewNameTranslator(); 
9    initViewResolvers(); 
10

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

1protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception 
2    ...
3    try { 
4        doDispatch(request, response); 
5    } 
6    ...
7

实际的请求分发由 doDispatch(request,response)来完成:

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

这样具体的 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 的框架实现作一个详细的分析。


关于作者: 王俊南(Jonas)

昨夜寒蛩不住鸣。惊回千里梦,已三更。起来独自绕阶行。人悄悄,帘外月胧明。 白首为功名。旧山松竹老,阻归程。欲将心事付瑶琴。知音少,弦断有谁听。

热门文章