`

SpringMVC深度探险(三) —— DispatcherServlet与初始化主线

 
阅读更多
本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/1341459

在上一篇文章中,我们给出了构成SpringMVC应用程序的三要素以及三要素的设计过程。让我们来归纳一下整个设计过程中的一些要点:

  • SpringMVC将Http处理流程抽象为一个又一个处理单元
  • SpringMVC定义了一系列组件(接口)与所有的处理单元对应起来
  • SpringMVC由DispatcherServlet贯穿始终,并将所有的组件串联起来
在整个过程中,组件和DispatcherServlet总是维持着一个相互支撑的关系:

  • DispatcherServlet —— 串联起整个逻辑主线,是整个框架的心脏
  • 组件 —— 逻辑处理单元的程序化表示,起到承上启下的作用,是SpringMVC行为模式的实际承载者
在本系列接下来的两篇文章中,我们将分别讨论DispatcherServlet和组件的相关内容。本文讨论DispatcherServlet,而下一篇则重点分析组件。

有关DispatcherServlet,我们想从构成DispatcherServlet的体系结构入手,再根据不同的逻辑主线分别加以分析,希望能够帮助读者整理出学习SpringMVC核心类的思路。

DispatcherServlet的体系结构

通过不同的角度来观察DispatcherServlet会得到不同的结论。我们在这里选取了三个不同的角度:运行特性、继承结构和数据结构。

【运行主线】

从DispatcherServlet所实现的接口来看,DispatcherServlet的核心本质:是一个Servlet。这个结论似乎很幼稚,不过这个幼稚的结论却蕴含了一个对整个框架都至关重要的内在原则:Servlet可以根据其特性进行运行主线的划分

根据Servlet规范的定义,Servlet中的两大核心方法init方法和service方法,它们的运行时间和触发条件都截然不同:

1. init方法

在整个系统启动时运行,且只运行一次。因此,在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。

2. service方法

在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。

因而在这里,Servlet的这一特性就被SpringMVC用于对不同的逻辑职责加以划分,从而形成两条互不相关的逻辑运行主线:

  • 初始化主线 —— 负责对SpringMVC的运行要素进行初始化
  • Http请求处理主线 —— 负责对SpringMVC中的组件进行逻辑调度完成对Http请求的处理
对于一个MVC框架而言,运行主线的划分非常重要。因为只有弄清楚不同的运行主线,我们才能针对不同的运行主线采取不同的研究策略。而我们在这个系列中的绝大多数分析的切入点,也是围绕着不同的运行主线进行的。

:SpringMVC运行主线的划分依据是Servlet对象中不同方法的生命周期。事实上,几乎所有的MVC都是以此为依据来进行运行主线的划分。这进一步可以证明所有的MVC框架的核心基础还是Servlet规范,而设计理念的差异也导致了不同的框架走向了完全不同的发展道路。

【继承结构】

除了运行主线的划分以外,我们再关注一下DispatcherServlet的继承结构:



在这个继承结构中,我们可以看到DispatcherServlet在其继承树中包含了2个Spring的支持类:HttpServletBean和FrameworkServlet。我们分别来讨论一下这两个Spring的支持类在这里所起到的作用。

HttpServletBean是Spring对于Servlet最低层次的抽象。在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将init-param中的值作为bean的属性注入进来:

public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}

	// Set bean properties from init parameters.
	try {
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
		initBeanWrapper(bw);
		bw.setPropertyValues(pvs, true);
	}
	catch (BeansException ex) {
		logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
		throw ex;
	}

	// Let subclasses do whatever initialization they like.
	initServletBean();

	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}


从源码中,我们可以看到HttpServletBean利用了Servlet的init方法的执行特性,将一个普通的Servlet与Spring的容器联系在了一起。在这其中起到核心作用的代码是:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入。BeanWrapper的相关知识属于Spring Framework的内容,我们在这里不做详细展开,读者可以具体参考HttpServletBean的注释获得更多的信息。

FrameworkServlet则是在HttpServletBean的基础之上的进一步抽象。通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中:

protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	} catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	} catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}


上面的这段代码就是FrameworkServlet初始化的核心代码。从中我们可以看到这个FrameworkServlet将调用其内部的方法initWebApplicationContext()对Spring的容器(WebApplicationContext)进行初始化。同时,FrameworkServlet还暴露了与之通讯的结构可供子类调用:

public abstract class FrameworkServlet extends HttpServletBean {

	/** WebApplicationContext for this servlet */
	private WebApplicationContext webApplicationContext;

        // 这里省略了其他所有的代码

	/**
	 * Return this servlet's WebApplicationContext.
	 */
	public final WebApplicationContext getWebApplicationContext() {
		return this.webApplicationContext;
	}
}


我们在这里暂且不对Spring容器(WebApplicationContext)的初始化过程详加探查,稍后我们会讨论一些WebApplicationContext初始化过程中的配置选项。不过读者可以在这里体会到:FrameworkServlet在其内部初始化了一个Spring的容器(WebApplicationContext)并暴露了相关的操作接口,因而继承自FrameworkServlet的DispatcherServlet,也就直接拥有了与WebApplicationContext进行通信的能力。

通过对DispatcherServlet继承结构的研究,我们可以明确:

downpour 写道
结论 DispatcherServlet的继承体系架起了DispatcherServlet与Spring容器进行沟通的桥梁。


【数据结构】

在上一篇文章中,我们曾经提到过DispatcherServlet的数据结构:



我们可以把在上面这张图中所构成DispatcherServlet的数据结构主要分为两类(我们在这里用一根分割线将其分割开来):

  • 配置参数 —— 控制SpringMVC组件的初始化行为方式
  • 核心组件 —— SpringMVC的核心逻辑处理组件
可以看到,这两类数据结构都与SpringMVC中的核心要素组件有关。因此,我们可以得出这样一个结论:

downpour 写道
结论 组件是整个DispatcherServlet的灵魂所在:它不仅是初始化主线中的初始化对象,同样也是Http请求处理主线中的逻辑调度载体。


:我们可以看到被我们划为配置参数的那些变量都是boolean类型的,它们将在DispatcherServlet的初始化主线中起到一定的作用,我们在之后会使用源码进行说明。而这些boolean值可以通过web.xml中的init-param值进行设定覆盖(这是由HttpServletBean的特性带来的)。

SpringMVC的运行体系

DispatcherServlet继承结构和数据结构,实际上表述的是DispatcherServlet与另外两大要素之间的关系:

  • 继承结构 —— DispatcherServlet与Spring容器(WebApplicationContext)之间的关系
  • 数据结构 —— DispatcherServlet与组件之间的关系
所以,其实我们可以这么说:SpringMVC的整个运行体系,是由DispatcherServlet、组件和容器这三者共同构成的。

在这个运行体系中,DispatcherServlet是逻辑处理的调度中心,组件则是被调度的操作对象。而容器在这里所起到的作用,是协助DispatcherServlet更好地对组件进行管理。这就相当于一个工厂招了一大批的工人,并把工人划分到一个统一的工作车间而便于管理。在工厂要进行生产活动时,只需要从工作车间把工人分派到相应的生产流水线上即可。

笔者在这里引用Spring官方reference中的一幅图,对三者之间的关系进行简单的描述:



:在这幅图中,我们除了看到在图的左半边DispatcherServlet、组件和容器这三者之间的调用关系以外,还可以看到SpringMVC的运行体系与其它运行体系之间存在着关系。有关这一点,我们在之后的讨论中会详细展开。

既然是三个元素之间的关系表述,我们必须以两两关系的形式进行归纳:

  • DispatcherServlet - 容器 —— DispatcherServlet对容器进行初始化
  • 容器 - 组件 —— 容器对组件进行全局管理
  • DispatcherServlet - 组件 —— DispatcherServlet对组件进行逻辑调用
值得注意的是,在上面这幅图中,三大元素之间的两两关系其实表现得并不明显,尤其是“容器 - 组件”和“DispatcherServlet - 组件”之间的关系。这主要是由于Spring官方reference所给出的这幅图是一个静态的关系表述,如果从动态的观点来对整个过程加以审视,我们就不得不将SpringMVC的运行体系与之前所提到的运行主线联系在一起,看看这些元素在不同的逻辑主线中所起到的作用。

接下来,我们就分别看看DispatcherServlet的两条运行主线。

DispatcherServlet的初始化主线

对于DispatcherServlet的初始化主线,我们首先应该明确几个基本观点:

  • 初始化主线的驱动要素 —— servlet中的init方法
  • 初始化主线的执行次序 —— HttpServletBean -> FrameworkServlet -> DispatcherServlet
  • 初始化主线的操作对象 —— Spring容器(WebApplicationContext)和组件
这三个基本观点,可以说是我们对之前所有讨论的一个小结。明确了这些内容,我们就可以更加深入地看看DispatcherServlet初始化主线的过程:



在这幅图中,我们站在一个动态的角度将DispatcherServlet、容器(WebApplicationContext)和组件这三者之间的关系表述出来,同时给出了这三者之间的运行顺序和逻辑过程。读者或许对其中的绝大多数细节还很陌生,甚至有一种无从下手的感觉。这没有关系,大家可以首先抓住图中的执行线,回忆一下之前有关DispatcherServlet的继承结构和数据结构的内容。接下来,我们就图中的内容逐一进行解释。

【WebApplicationContext的初始化】

之前我们讨论了DispatcherServlet对于WebApplicationContext的初始化是在FrameworkServlet中完成的,不过我们并没有细究其中的细节。在默认情况下,这个初始化过程是由web.xml中的入口程序配置所驱动的:

<!-- Processes application requests -->
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>
		
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/**</url-pattern>
</servlet-mapping>


我们已经不止一次提到过这段配置,不过在这之前都没有对这段配置做过什么很详细的分析。事实上,这段入口程序的配置中隐藏了SpringMVC的两大要素(核心分发器Dispatcher和核心配置文件[servlet-name]-servlet.xml)之间的关系表述:

downpour 写道
在默认情况下,web.xml配置节点中<servlet-name>的值就是建立起核心分发器DispatcherServlet与核心配置文件之间联系的桥梁。DispatcherServlet在初始化时会加载位置在/WEB-INF/[servlet-name]-servlet.xml的配置文件作为SpringMVC的核心配置。


SpringMVC在这里采用了一个“命名约定”的方法进行关系映射,这种方法很廉价也很管用。以上面的配置为例,我们就必须在/WEB-INF/目录下,放一个名为dispatcher-servlet.xml的Spring配置文件作为SpringMVC的核心配置用以指定SpringMVC的基本组件声明定义。

这看上去似乎有一点别扭,因为在实际项目中,我们通常喜欢把配置文件放在classpath下,并使用不同的package进行区分。例如,在基于Maven的项目结构中,所有的配置文件应置于src/main/resources目录下,这样才比较符合配置文件统一化管理的最佳实践。

于是,Spring提供了一个初始化的配置选项,通过指定contextConfigLocation选项来自定义SpringMVC核心配置文件的位置:

<!-- Processes application requests -->
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
		
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>


这样一来,DispatcherServlet在初始化时,就会自动加载在classpath下,web这个package下名为applicationContext-dispatcherServlet.xml的文件作为其核心配置并用以初始化容器(WebApplicationContext)。

当然,这只是DispatcherServlet在进行WebApplicationContext初始化过程中的配置选项之一。我们可以在Spring的官方reference中找到相应的配置选项,有兴趣的读者可以参照reference的说明进行尝试:



所有的这些配置选项,实际上都是为了让DispatcherServlet对WebApplicationContext的初始化过程显得更加自然。不过这只是完成了容器(WebApplicationContext)的构建工作,那么容器所管理的那些组件,又是如何进行初始化的呢?

downpour 写道
结论 SpringMVC核心配置文件中所有的bean定义,就是SpringMVC的组件定义,也是DispatcherServlet在初始化容器(WebApplicationContext)时,所要进行初始化的组件。


在上一篇文章我们谈到组件的时候就曾经提到,SpringMVC自身对于组件并未实现一套完整的管理机制,而是借用了Spring Framework核心框架中容器的概念,将所有的组件纳入到容器中进行管理。所以,SpringMVC的核心配置文件使用与传统Spring Framework相同的配置形式,而整个管理的体系也是一脉相承的。

:Spring3.0之后,单独为SpringMVC建立了专用的schema,从而使得我们可以使用Schema Based XML来对SpringMVC的组件进行定义。不过我们可以将其视作是传统Spring配置的一个补充,而不要过于纠结不同的配置格式。

我们知道,SpringMVC的组件是一个个的接口定义,当我们在SpringMVC的核心配置文件中定义一个组件时,使用的却是组件的实现类:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/" />
	<property name="suffix" value=".jsp" />
</bean>


这也就是Spring管理组件的模式:用具体的实现类来指定接口的行为方式。不同的实现类,代表着不同的组件行为模式,它们在Spring容器中是可以共存的:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/" />
	<property name="suffix" value=".jsp" />
</bean>

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
	<property name="prefix" value="/" />
	<property name="suffix" value=".ftl" />
</bean>


所以,Spring的容器就像是一个聚宝盆,它只负责承载对象,管理好对象的生命周期,而并不关心一个组件接口到底有多少种实现类或者行为模式。这也就是我们在上面那幅图中,画了多个HandlerMappings、HandlerAdapters和ViewResolvers的原因:一个组件的多种行为模式可以在容器中共存,容器将负责对这些实现类进行管理。而具体如何使用这些对象,则由应用程序自身来决定。

如此一来,我们可以大致概括一下WebApplicationContext初始化的两个逻辑层次:

  • DispatcherServlet负责对容器(WebApplicationContext)进行初始化。
  • 容器(WebApplicationContext)将读取SpringMVC的核心配置文件进行组件的实例化。
整个过程,我们把应用程序的日志级别调低,可以进行非常详细的观察:

引用
14:15:27,037 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment
14:15:27,128 DEBUG DispatcherServlet:115 - Initializing servlet 'dispatcher'
14:15:27,438  INFO DispatcherServlet:444 - FrameworkServlet 'dispatcher': initialization started

14:15:27,449 DEBUG DispatcherServlet:572 - Servlet with name 'dispatcher' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [null]
1571 [main] INFO /sample - Initializing Spring FrameworkServlet 'dispatcher'
14:15:27,505 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment
14:15:27,546  INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:27,689  INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml]
14:15:27,872 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas]
14:15:28,442 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller]
14:15:28,442 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class]
14:15:28,450 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]]
14:15:28,569 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class]
14:15:28,571 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]
14:15:28,634 DEBUG BeanDefinitionParserDelegate:497 - Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0]
14:15:28,635 DEBUG XmlBeanDefinitionReader:216 - Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml]
14:15:28,635 DEBUG XmlWebApplicationContext:525 - Bean factory for WebApplicationContext for namespace 'dispatcher-servlet': org.springframework.beans.factory.support.DefaultListableBeanFactory@2321b59a: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy
14:15:29,015 DEBUG RequestMappingHandlerMapping:98 - Looking for request mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:29,037  INFO RequestMappingHandlerMapping:188 - Mapped "{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.BlogController.index()
14:15:29,039  INFO RequestMappingHandlerMapping:188 - Mapped "{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.UserController.login(java.lang.String,java.lang.String)
14:15:29,040 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0'
14:15:29,460 DEBUG BeanNameUrlHandlerMapping:71 - Looking for URL mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy
14:15:29,539 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
14:15:29,540 DEBUG DefaultListableBeanFactory:217 - Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0'
14:15:29,555  INFO SimpleUrlHandlerMapping:314 - Mapped URL path [/static/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
14:15:29,556 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0'
14:15:29,827 DEBUG DispatcherServlet:523 - Published WebApplicationContext of servlet 'dispatcher' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher]
14:15:29,827  INFO DispatcherServlet:463 - FrameworkServlet 'dispatcher': initialization completed in 2389 ms
14:15:29,827 DEBUG DispatcherServlet:136 - Servlet 'dispatcher' configured successfully

4047 [main] INFO org.mortbay.log - Started SelectChannelConnector@0.0.0.0:8080
Jetty Server started, use 4267 ms


在这段启动日志(笔者进行了一定删减)中,笔者刻意将WebApplicationContext的初始化的标志日志使用红色的标进行区分,而将核心配置文件的读取位置和组件定义初始化的标志日志使用蓝色标记加以区分。相信有了这段日志的帮助,读者应该可以对WebApplicationContext的初始化过程有了更加直观的认识。

:启动日志是我们研究SpringMVC的主要途径之一,之后我们还将反复使用这种方法对SpringMVC的运行过程进行研究。读者应该仔细品味每一条日志的作用,从而能够与之后的分析讲解呼应起来。

或许读者对WebApplicationContext对组件进行初始化的过程还有点困惑,大家不妨先将这个过程省略,把握住整个DispatcherServlet的大方向。我们在之后的文章中,还将对SpringMVC的组件、这些组件的定义以及组件的初始化方式做进一步的分析和探讨。

到此为止,图中顺着FrameworkServlet的那些箭头,我们已经交代清楚,读者可以回味一下整个过程。

【独立的WebApplicationContext体系】

独立的WebApplicationContext体系,是SpringMVC初始化主线中的一个非常重要的概念。回顾一下刚才曾经提到过的DispatcherServlet、容器和组件三者之间的关系,我们在引用的那副官方reference的示意图中,实际上已经包含了这一层意思:

downpour 写道
结论 在DispatcherServlet初始化的过程中所构建的WebApplicationContext独立于Spring自身的所构建的其他WebApplicationContext体系而存在。


稍有一些Spring编程经验的程序员,对于下面的配置应该非常熟悉:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:context/applicationContext-*.xml</param-value>
</context-param>
	
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


在上面的代码中,我们定义了一个Listener,它会在整个Web应用程序启动的时候运行一次,并初始化传统意义上的Spring的容器。这也是一般情况下,当并不使用SpringMVC作为我们的表示层解决方案,却希望在我们的Web应用程序中使用Spring相关功能时所采取的一种配置方式。

如果我们要在这里引入SpringMVC,整个配置看上去就像这样:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:context/applicationContext-*.xml</param-value>
</context-param>
	
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
    
<!-- Processes application requests -->
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
		
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>


在这种情况下,DispatcherServlet和ContextLoaderListener会分别构建不同作用范围的容器(WebApplicationContext)。我们可以引入两个不同的概念来对其进行表述:ContextLoaderListener所初始化的容器,我们称之为Root WebApplicationContext;而DispatcherServlet所初始化的容器,是SpringMVC WebApplicationContext

同样采取日志分析的方法,加入了ContextLoaderListener之后,整个启动日志变成了这样:

引用
[main] INFO /sample - Initializing Spring root WebApplicationContext
14:56:42,261  INFO ContextLoader:272 - Root WebApplicationContext: initialization started
14:56:42,343 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment
14:56:42,365  INFO XmlWebApplicationContext:495 - Refreshing Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy
14:56:42,441 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\context]
14:56:42,442 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\context] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/context/applicationContext-*.xml]
14:56:42,446 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath:context/applicationContext-*.xml] to resources [file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml]]
14:56:42,447  INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml]
14:56:42,486 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas]
14:56:42,597 DEBUG DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions
14:56:42,658 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample]
14:56:42,699 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\service\impl\BlogServiceImpl.class]
14:56:42,750 DEBUG XmlBeanDefinitionReader:216 - Loaded 5 bean definitions from location pattern [classpath:context/applicationContext-*.xml]
14:56:42,750 DEBUG XmlWebApplicationContext:525 - Bean factory for Root WebApplicationContext: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327: defining beans [blogService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
14:56:42,860 DEBUG ContextLoader:296 - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
14:56:42,860  INFO ContextLoader:301 - Root WebApplicationContext: initialization completed in 596 ms


14:56:42,935 DEBUG DispatcherServlet:115 - Initializing servlet 'dispatcher'
14:56:42,974  INFO DispatcherServlet:444 - FrameworkServlet 'dispatcher': initialization started

14:56:42,974 DEBUG DispatcherServlet:572 - Servlet with name 'dispatcher' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy]
14:56:42,979  INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:42,983  INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml]
14:56:42,987 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas]
14:56:43,035 DEBUG DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions
14:56:43,075 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller]
14:56:43,075 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class]
14:56:43,077 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]]
14:56:43,079 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class]
14:56:43,080 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]
14:56:43,089 DEBUG XmlBeanDefinitionReader:216 - Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml]
14:56:43,089 DEBUG XmlWebApplicationContext:525 - Bean factory for WebApplicationContext for namespace 'dispatcher-servlet': org.springframework.beans.factory.support.DefaultListableBeanFactory@5e6458a6: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327
14:56:43,323 DEBUG RequestMappingHandlerMapping:98 - Looking for request mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:43,345  INFO RequestMappingHandlerMapping:188 - Mapped "{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.BlogController.index()
14:56:43,346  INFO RequestMappingHandlerMapping:188 - Mapped "{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.UserController.login(java.lang.String,java.lang.String)
14:56:43,707 DEBUG BeanNameUrlHandlerMapping:71 - Looking for URL mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
14:56:43,828  INFO SimpleUrlHandlerMapping:314 - Mapped URL path [/static/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
14:56:43,883 DEBUG DispatcherServlet:523 - Published WebApplicationContext of servlet 'dispatcher' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher]
14:56:43,883  INFO DispatcherServlet:463 - FrameworkServlet 'dispatcher': initialization completed in 909 ms
14:56:43,883 DEBUG DispatcherServlet:136 - Servlet 'dispatcher' configured successfully

2687 [main] INFO org.mortbay.log - Started SelectChannelConnector@0.0.0.0:8080
Jetty Server started, use 2901 ms


整个启动日志被我们分为了2段。第一段的过程初始化的是Root WebApplicationContext;而第二段的过程初始化的是SpringMVC的WebApplicationContext。我们还是使用了红色的标记和蓝色标记指出了在整个初始化过程中的一些重要事件。其中,有这样一段内容值得我们注意:

引用
14:56:42,979  INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext


在这段日志中,非常明确地指出了SpringMVC WebApplicationContext与Root WebApplicationContext之间的关系:从属关系。因为根据这段日志的表述,SpringMVC WebApplicationContext能够感知到Root WebApplicationContext的存在,并且将其作为parent容器。

Spring正是使用这种Parent-Child的容器关系来对不同的编程层次进行划分。这种我们俗称的父子关系实际上不仅仅是一种从属关系,更是一种引用关系。从刚才的日志分析中,我们可以看出:SpringMVC中所定义的一切组件能够无缝地与Root WebApplicationContext中的组件整合。

到此为止,我们针对图中以web.xml为核心的箭头分支进行了讲解,读者可以将图中的内容与上面的文字说明对照再次加以理解。

【组件默认行为的指定】

DispatcherServlet的初始化主线的执行体系是顺着其继承结构依次进行的,我们在之前曾经讨论过它的执行次序。所以,只有在FrameworkServlet完成了对于WebApplicationContext和组件的初始化之后,执行权才被正式转移到DispatcherServlet中。我们可以来看看DispatcherServlet此时究竟干了哪些事:

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}


onRefresh是FrameworkServlet中预留的扩展方法,在DispatcherServlet中做了一个基本实现:initStrategies。我们粗略一看,很容易就能明白DispatcherServlet到底在这里干些什么了:初始化组件

读者或许会问,组件不是已经在WebApplicationContext初始化的时候已经被初始化过了嘛?这里所谓的组件初始化,指的又是什么呢?让我们来看看其中的一个方法的源码:

/**
 * Initialize the MultipartResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * no multipart handling is provided.
 */
private void initMultipartResolver(ApplicationContext context) {
	try {
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	} catch (NoSuchBeanDefinitionException ex) {
		// Default is no multipart resolver.
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
					"': no multipart request handling provided");
		}
	}
}


原来,这里的初始化,指的是DispatcherServlet从容器(WebApplicationContext)中读取组件的实现类,并缓存于DispatcherServlet内部的过程。还记得我们之前给出的DispatcherServlet的数据结构吗?这些位于DispatcherServlet内部的组件实际上只是一些来源于容器缓存实例,不过它们同样也是DispatcherServlet进行后续操作的基础。

:我们在第一篇文章中就曾经提到过Servlet实例内部的属性的访问有线程安全问题。而在这里,我们可以看到所有的组件都以Servlet内部属性的形式被调用,充分证实了这些组件本身也都是无状态的单例对象,所以我们在这里不必考虑线程安全的问题。

如果对上面的代码加以详细分析,我们会发现initMultipartResolver的过程是查找特定MultipartResolver实现类的过程。因为在容器中查找组件的时候,采取的是根据特定名称(MULTIPART_RESOLVER_BEAN_NAME)进行查找的策略。由此,我们可以看到DispatcherServlet进行组件初始化的特点:

downpour 写道
结论 DispatcherServlet中对于组件的初始化过程实际上是应用程序在WebApplicationContext中选择和查找组件实现类的过程,也是指定组件在SpringMVC中的默认行为方式的过程。


除了根据特定名称进行查找的策略以外,我们还对DispatcherServlet中指定SpringMVC默认行为方式的其他的策略进行的总结:

  • 名称查找 —— 根据bean的名字在容器中查找相应的实现类
  • 自动搜索 —— 自动搜索容器中所有某个特定组件(接口)的所有实现类
  • 默认配置 —— 根据一个默认的配置文件指定进行实现类加载
这三条策略恰巧在initHandlerMappings的过程中都有体现,读者可以从其源码中找到相应的线索:

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;

	if (this.detectAllHandlerAdapters) {
		// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
			// We keep HandlerAdapters in sorted order.
			OrderComparator.sort(this.handlerAdapters);
		}
	}
	else {
		try {
			HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
			this.handlerAdapters = Collections.singletonList(ha);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerAdapter later.
		}
	}

	// Ensure we have at least some HandlerAdapters, by registering
	// default HandlerAdapters if no other adapters are found.
	if (this.handlerAdapters == null) {
		this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
		}
	}
}


这里有必要对“默认策略”做一个简要的说明。SpringMVC为一些核心组件设置了默认行为方式的说明,这个说明以一个properties文件的形式位于SpringMVC分发包(例如spring-webmvc-3.1.0.RELEASE.jar)的内部:



我们可以观察一下DispatcherServlet.properties的内容:

引用
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager


结合刚才initHandlerMappings的源码,我们可以发现如果没有开启detectAllHandlerAdapters选项或者根据HANDLER_ADAPTER_BEAN_NAME的名称没有找到相应的组件实现类,就会使用DispatcherServlet.properties文件中对于HandlerMapping接口的实现来进行组件默认行为的初始化。

由此可见,DispatcherServlet.properties中所指定的所有接口的实现方式在Spring的容器WebApplicationContext中总有相应的定义。这一点,我们在组件的讨论中还会详谈。

这个部分我们的侧重点是图中DispatcherServlet与容器之间的关系。读者需要理解的是图中为什么会有两份组件定义,它们之间的区别在哪里,以及DispatcherServlet在容器中查找组件的三种策略。

小结

在本文中,我们对SpringMVC的核心类:DispatcherServlet进行了一番梳理。也对整个SpringMVC的两条主线之一的初始化主线做了详细的分析。

对于DispatcherServlet而言,重要的其实并不是这个类中的代码和逻辑,而是应该掌握这个类在整个框架中的作用以及与SpringMVC中其他要素的关系。

对于初始化主线而言,核心其实仅仅在于那张笔者为大家精心打造的图。读者只要掌握了这张图,相信对整个SpringMVC的初始化过程会有一个全新的认识。

===================== 华丽的分割线 =====================

小广告:我的新书《Struts2技术内幕》已经上市,其中涵盖了Web开发的基本观点以及对Struts2的深入研究。具体请参照:《Struts2技术内幕》新书上市
  • 大小: 6.4 KB
  • 大小: 29.9 KB
  • 大小: 52.4 KB
  • 大小: 51.4 KB
  • 大小: 33.4 KB
  • 大小: 16.8 KB
分享到:
评论
31 楼 su_chunlong 2017-08-07  
rrredriver 写道
极赞的文章,感谢楼主!
有一个问题想请帮助释惑,就是讲DispatcherServlet的继承结构那一段有一句“通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中”,这里“引入到Servlet对象之中”是什么意思,是指通过继承来扩展Servlet基类,增加了WebApplicationContext这个属性么?谢谢!

应该是在servlet的上下文中保存了spring的上下文
30 楼 rrredriver 2017-01-15  
极赞的文章,感谢楼主!
有一个问题想请帮助释惑,就是讲DispatcherServlet的继承结构那一段有一句“通过FrameworkServlet真正初始化了一个Spring的容器(WebApplicationContext),并引入到Servlet对象之中”,这里“引入到Servlet对象之中”是什么意思,是指通过继承来扩展Servlet基类,增加了WebApplicationContext这个属性么?谢谢!
29 楼 z550450124 2016-11-28  
写的太好了,,非常感谢
28 楼 shenkun58 2016-06-17  
 
27 楼 hanxueyuma 2015-01-30  
感谢楼主,非常受益
26 楼 woodpeckerboy 2014-09-12  
大哥。看你的博客真的很受益。非常感谢您的这篇文章。
25 楼 空谷悠悠 2013-12-28  
hi downpour!
    仔细拜读了大神的springMVC系列的博客,收益颇多,另外我在工作中使用spring mvc存在一个困扰,一直没有找到理想的解决办法,希望大神看到后能够指点一二,鄙人将不胜感觉啊啊啊啊。
    首先我来描述下工程情况,spring版本为3.1.3,包含的spring配置文件spring-context*.xml及spring-mvc.xml,相应的配置信息放在了多个*.properties文件中。spring.xml中加载了所有的properties文件,然后scan了工程中的controller包,spring-context*.xml中加载了所有的properties文件,然后scan了工程中的非controller包,web.xml中的DispatcherServlet中的contextConfigLocation值为spring-mvc.xml,ContextLoaderListener的contextConfigLocation值为spring-context*.xml。这样工程启动时,会分别在加载spring-mvc.xml和spring-context*.xml时去加载propertes文件和scan工程包(一个动作分别作了两次)。
     这里我的疑问也产生了,启动容器时,为什么不能把properties文件的加载及工程包的scan动作一次完成呢?最开始即按照这一目标去配置的,尝试把加载properties即scan所有包的动作放到spring-mvc.xml中完成,但加载spring-context.xml时找不到需要的properties文中配置的健值。还试了其它几种方法,也是不行。
    
24 楼 shiyixunsyx 2013-11-06  
这本书出版了吗?怎么当当上没找到呢?
23 楼 373045912 2013-09-15  
引用
由此可见,DispatcherServlet.properties中所指定的所有接口的实现方式在Spring的容器WebApplicationContext中总有相应的定义。这一点,我们在组件的讨论中还会详谈。


这点感觉有问题 

如果是加载这些默认的这些组件时  是不放入容器内管理的 因为它调用的是AbstractAutowireCapableBeanFactory的createBean方法:
	@SuppressWarnings("unchecked")
	public <T> T createBean(Class<T> beanClass) throws BeansException {
		// Use prototype bean definition, to avoid registering bean as dependent bean.
		RootBeanDefinition bd = new RootBeanDefinition(beanClass);
		bd.setScope(SCOPE_PROTOTYPE);
		return (T) createBean(beanClass.getName(), bd, null);
	}


这个是不放入容器内的
22 楼 373045912 2013-09-15  
373045912 写道
引用
SpringMVC在这里采用了一个“命名约定”的方法进行关系映射,这种方法很廉价也很管用。以上面的配置为例,我们就必须在/WEB-INF/目录下,放一个名为dispatcher-servlet.xml的Spring配置文件作为SpringMVC的核心配置用以指定SpringMVC的基本组件声明定义。


这个我感觉有点问题:
  FrameworkServlet在生成createWebApplicationContext的时候:
 
		wac.setParent(parent);
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.setConfigLocation(getContextConfigLocation());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		postProcessWebApplicationContext(wac);
		wac.refresh();

		return wac;

   其中这个setNamespace便是默认的一个约束文件名  也即楼主说的/WEB-INF/[servlet-name]-servlet.xml
  
   但是同样可以通过setConfigLocation设置这个配置文件的名字 这个是在FrameworkServlet被调用的:
  
	public void setContextConfigLocation(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

   大家可以很疑惑这个方法是在哪地方被调用的,就是在HttpServletBean这个servlet初始化的时候,
   
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

      Spring的BeanWrapper这个包装器很给力很强大,一旦这个servlet配置中有contextConfigLocation这个init-param参数  便会调用setContextConfigLocation方法

       底下通过XmlWebApplicationContext在实例化内部的DefaultListableBeanFactory就豁然开朗了 ,XmlWebApplicationContext在getConfigLocations的时候 
      
	protected String[] getConfigLocations() {
		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
	}

       如果没读取到configLocations这个参数  才开始调用getDefaultConfigLocations方法 而这个 就是那个默认的[servlet-name]-servlet.xml文件
   
       所以 DispatcherServlet在这个点上 默认采取了命名约定的方式 如果没读取到  那个只要在init-param配置好contextConfigLocation也是OK的


       刚才没看完文章就回复了 原来楼主底下有相关阐述了~
21 楼 373045912 2013-09-15  
引用
SpringMVC在这里采用了一个“命名约定”的方法进行关系映射,这种方法很廉价也很管用。以上面的配置为例,我们就必须在/WEB-INF/目录下,放一个名为dispatcher-servlet.xml的Spring配置文件作为SpringMVC的核心配置用以指定SpringMVC的基本组件声明定义。


这个我感觉有点问题:
  FrameworkServlet在生成createWebApplicationContext的时候:
 
		wac.setParent(parent);
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.setConfigLocation(getContextConfigLocation());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		postProcessWebApplicationContext(wac);
		wac.refresh();

		return wac;

   其中这个setNamespace便是默认的一个约束文件名  也即楼主说的/WEB-INF/[servlet-name]-servlet.xml
  
   但是同样可以通过setConfigLocation设置这个配置文件的名字 这个是在FrameworkServlet被调用的:
  
	public void setContextConfigLocation(String contextConfigLocation) {
		this.contextConfigLocation = contextConfigLocation;
	}

   大家可以很疑惑这个方法是在哪地方被调用的,就是在HttpServletBean这个servlet初始化的时候,
   
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

      Spring的BeanWrapper这个包装器很给力很强大,一旦这个servlet配置中有contextConfigLocation这个init-param参数  便会调用setContextConfigLocation方法

       底下通过XmlWebApplicationContext在实例化内部的DefaultListableBeanFactory就豁然开朗了 ,XmlWebApplicationContext在getConfigLocations的时候 
      
	protected String[] getConfigLocations() {
		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
	}

       如果没读取到configLocations这个参数  才开始调用getDefaultConfigLocations方法 而这个 就是那个默认的[servlet-name]-servlet.xml文件
   
       所以 DispatcherServlet在这个点上 默认采取了命名约定的方式 如果没读取到  那个只要在init-param配置好contextConfigLocation也是OK的
20 楼 diujiujiu 2013-08-18  
楼主大神,我想问问分发器的映射路径/和/*以及/**有什么不同!我都试了,只有/可以用,还有*.do之类的后缀通配符可以用
19 楼 yan453598503 2013-04-23  
灰常好的书,书已经出了吗??书名叫什么?我去买一本,太值 了 ,谢谢!!!
18 楼 cagegao 2012-09-20  
楼主写得很透彻啊! 拜读以后对SpringMVC的底层有了深刻的理解和认识!
套用一句老话:如拨云见日,醍醐灌顶啊!
17 楼 xustmath 2012-09-03  
873339698 写道
楼主你好, 有句话没有看懂(标成红色),希望您能帮小弟解释下...

我知道servlet内部属性,不是线程安全的,那这里的组件也是以内部属性调用,为什么就证明了他们本身是无状态的单例对象? 

谢谢


而在这里,我们可以看到所有的组件都以Servlet内部属性的形式被调用,充分证实了这些组件本身也都是无状态的单例对象,所以我们在这里不必考虑线程安全的问题。


这就和你在service中调用dao的原理是一样的。单例对象之间通过内部属性的方式进行调用,不需要考虑线程安全问题,因为这里面不存在对单例对象内部属性的状态修改。

你可以参考我另外一篇有关ThreadLocal的博客,里面对线程安全的概念有一番表述。



springMVC和struts的两种处理方式本质区别在哪里,ThreadLocal会不会效率更低一些?
16 楼 xwqiang 2012-08-15  
原来都有了next了 !
15 楼 liberD 2012-07-03  
downpour 写道
mojunbin 写道
楼主,市面上SpringMVC的讲解,当属您的最好。另外说说,您的Struts2技术内幕这本书也是相当的好啊!


今年年内会出版一本有关SpringMVC的书。



马上出吧。出了立马买一本。哈哈
14 楼 downpour 2012-06-19  
mojunbin 写道
楼主,市面上SpringMVC的讲解,当属您的最好。另外说说,您的Struts2技术内幕这本书也是相当的好啊!


今年年内会出版一本有关SpringMVC的书。
13 楼 mojunbin 2012-06-18  
楼主,市面上SpringMVC的讲解,当属您的最好。另外说说,您的Struts2技术内幕这本书也是相当的好啊!
12 楼 Eywa 2012-06-05  

相关推荐

    Spring框架系列(13) - SpringMVC实现原理之DispatcherServlet的初始化过程.doc

    SpringMVC DispatcherServlet 初始化过程详解 DispatcherServlet 是 SpringMVC 框架中的核心组件,对于 SpringMVC 的请求处理和响应起着至关重要的作用。DispatcherServlet 的初始化过程是 SpringMVC 实现原理的...

    SpringMVC源码剖析(三)- DispatcherServlet的初始化流程1

    在本文中,我们将深入探讨`DispatcherServlet`的初始化流程,这是SpringMVC的核心组件。`DispatcherServlet`扮演着中央调度者的角色,负责接收请求、解析请求信息,并调用合适的控制器进行业务逻辑处理。 首先,让...

    Spring源码学习九:DispatcherServlet初始化源码分析1

    DispatcherServlet的初始化过程可以分为三个阶段:init方法阶段、initServletBean方法阶段和onRefresh方法阶段。在init方法阶段,DispatcherServlet会继承HttpServletBean的init方法,并重写initServletBean方法。在...

    SpringMVC学习(一)——SpringMVC入门小程序

    在SpringMVC的配置方面,我们需要创建一个`web.xml`文件来初始化DispatcherServlet,并配置视图解析器和其他相关组件。此外,还可以通过Java配置类替换XML配置,使用`@EnableWebMvc`注解开启SpringMVC的功能。 开发...

    03springmvc注解驱动开发的servlet3.0初始化配置类.avi

    03springmvc注解驱动开发的servlet3.0初始化配置类.avi

    springMVC整合MyBatis——SSM

    下面将详细讲解这三个组件及其整合过程。 **Spring框架** Spring是一个全面的企业级应用开发框架,它提供了依赖注入(DI)和面向切面编程(AOP)等核心功能。在SSM架构中,Spring作为基础,负责管理应用程序的bean...

    Spring+SpringMVC+Mybatis框架整合例子——亲测可用.zip

    SSM框架整合是Java开发中常见的技术栈,包括Spring、SpringMVC和Mybatis三个核心组件。这个压缩包提供了一个已经验证过的整合示例,帮助开发者理解和实践这三大框架的协同工作。 首先,Spring框架是Java企业级应用...

    深度解析springMvc

    ### 深度解析Spring MVC:初始化流程及关键机制 #### 一、Spring MVC简介 Spring MVC 是 Spring Framework 的一个重要模块,它实现了基于 Web 的 MVC(Model-View-Controller)设计模式,主要用于构建灵活且可扩展...

    SpringMVC URL 与 Controller 方法 初始化 源码赏析.vsdx

    SpringMVC URL 与 Controller 方法初始化源码流程 Visio 文档 文档可以直接通过Visio进行编辑,方便二次修改、学习

    SpringMVC学习(四)——Spring、MyBatis和SpringMVC的整合

    在本教程中,我们将深入探讨如何将Spring框架、MyBatis持久层框架和SpringMVC Web框架整合到一个项目中,以实现高效、模块化的Web应用程序开发。这三者结合可以构建出强大的企业级应用,提供了优秀的数据访问、业务...

    SpringMVC 处置流程分析

    总结,SpringMVC的处置流程始于Web应用的初始化,包括ContextLoaderListener加载根上下文和DispatcherServlet初始化子上下文。接着,DispatcherServlet负责接收和分发请求,通过HandlerMapping找到处理器,...

    springMVC初步

    在web.xml中,你可以设置DispatcherServlet的初始化参数,指明SpringMVC的配置文件位置。在SpringMVC配置文件中,你可以定义HandlerMapping、HandlerAdapter、ViewResolver等组件。 例如,一个简单的Controller可以...

    从源码的角度来看SpringMVC.pdf

    DispatcherServlet的初始化流程涉及多个步骤,通过org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext 方法初始化WebApplicationContext,然后调用onRefresh方法进行refresh操作。...

    springMVC学习——注解方式

    在这个"springMVC学习——注解方式"的主题中,我们将深入探讨如何使用注解来简化SpringMVC的配置和开发流程。 首先,让我们了解一下什么是注解。在Java中,注解是一种元数据,允许程序员在源代码中嵌入信息,这些...

    SpringMVC PPT_springmvc_

    SpringMVC 是一款基于 Java 的轻量级 Web 开发框架,它是 Spring 框架的重要组成部分,主要用于构建 MVC(Model-View-Controller)模式的 Web 应用程序。本教程将深入探讨 SpringMVC 的核心概念、配置以及实际应用。...

    SpringMVC——执行原理及简单配置实现.docx

    2. Web.xml配置文件:在这里,我们通常会配置DispatcherServlet,指定它的servlet-name、servlet-class以及初始化参数,用于加载SpringMVC的配置文件。例如,以下是一个基本的DispatcherServlet配置: ```xml ...

    Spring MVC启动时初始化的几个常用方法

    1. **加载配置**:`DispatcherServlet`会在初始化阶段读取配置文件(如`servlet-context.xml`),通过`WebApplicationContext`加载Bean定义。这个过程中,你可以自定义拦截器、视图解析器、异常处理器等关键组件。 ...

    Spring3+springmvc+mybatis三大整合

    在Java开发领域,Spring、SpringMVC和MyBatis是三个非常重要的开源框架,它们各自在不同的层次上解决了Web应用中的问题。Spring作为全面的框架,提供了依赖注入(DI)和面向切面编程(AOP)等核心特性;SpringMVC是...

Global site tag (gtag.js) - Google Analytics