基于Spring MVC的Web应用开发(1) - HelloWorld

wuyabing 2012-03-16

MVC架构

MVC是模型(model),视图(view),控制器(controller)3个单词的首字母缩写。有些应用需要处理用户请求并操纵和显示数据,MVC模式可以简化其实现。该模式由3个组件组成:

  • 模型表示用户期望看到的数据。通常情况下,模型由JavaBean组成。
  • 视图负责显示模型。文本编辑器中的视图组件会以恰当的格式显示一段文本,视图在Web应用中会生成客户端浏览器可以解释显示的HTML。
  • 控制器表示逻辑代码,负责处理请求和执行用户的意图。它会构建恰当的模型并将其传入视图进行显示。对Java Web应用来说,控制器多是一个servlet。当然,控制器可以使用任意语言实现,只要web容器支持就行。
Spring MVC介绍
Spring MVC对MVC的实现相当通用。模型就是一个包含数据的简单Map,视图就是一个interface,由它实现显示数据,控制器是Controller接口的实现。
Spring对Web应用下MVC模式的实现基于spring-webmvc下的org.springframework.web.servlet.DispatcherServlet。该Servlet会处理请求并调用恰当的控制元素对请求进行处理。Dispatcher会拦截请求并决定由哪个控制器处理请求。Spring控制器的handling方法会返回一个ModelAndView实例。该实例持有对视图和模型的引用。模型就是一个简单的Map实例,它里面是JavaBean,View接口会显示这些JavaBean。View接口定义了render方法。它遵循这样的原则:View的实现可以是任何东西,只要客户端能对其进行解析就行。
MVC实现
如果要用Spring创建Web应用,需要从基本的web.xml开始,我们在其中指定了DispatcherServlet并为其设定好相应的url-pattern。代码清单如下:
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- Disables Servlet Container welcome file handling. Needed for compatibility with Servlet 3.0 and Tomcat 7.0 -->
	<welcome-file-list>
		<welcome-file></welcome-file>
	</welcome-file-list>

</web-app>
 上述web.xml文件定义了DispatcherServlet类作为名叫appServlet的Servlet,映射所有请求至该Servlet。
使用处理器映射
对于具体某个请求,Web应用如何知道该调用哪个servlet或者handler实现呢?Spring handler映射就在这里发挥作用了。只需要几步就可以为Spring控制器配置URL映射,因为我们使用的是Spring3.1,@Controller注解专门为此而生。
在具体展示代码前,先说明一下Spring的内部实现原理,Spring使用org.springframework.web.servlet.HandlerMappping这个接口的实现来标识调用的控制器,该接口提供了好多中实现,现在我们只要讲解hello world中需要用到的SimpleUrlHandlerMapping,凭借该handler映射,我们可以在请求中(使用全名和通配符)指定处理该请求的控制器。下列清单展示了一个handler映射的例子:
package org.springframework.samples.mvc.simple;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SimpleController {

	@RequestMapping("/simple")
	@ResponseBody
	public String simple() {
		return "Hello world!";
	}
	
}
 Spring控制器
控制器负责处理请求:根据request构建model并将其传到view以进行显示。
DispatcherServlet有一个List类型的handlerAdapters属性,它可用来指定你想用的HandlerAdapter实现,如果这个属性值为null,就会使用默认的策略。这句话暂时不明白没关系,下文会有详细解读。
上面的代码清单其实已经继承一个控制器实现。在启动的时候我们会注意到有这样的输出日志:
INFO: Mapped URL path [/simple] onto handler 'simpleController'
INFO: Mapped URL path [/simple.*] onto handler 'simpleController'
INFO: Mapped URL path [/simple/] onto handler 'simpleController'
 因为我们在web.xml中做了全局性的URL通配符,所以这里Spring“智能”的为我们的@RequestMapping("/simple")映射好三种URL(如何只保留其中一种,以后再讲)。
视图
hello world并没有视图,因为我们使用了@ResponseBody注解,该注解直接将控制器中的返回值绑定到web响应体里,如果返回的是字符串,那么该字符串就显示在浏览器上,而这也就是我们想要的。上面例子的结果是将文本hello world写入http响应流中。下面将spring官方手册中有关@ResponseBody的解释翻译一下:
@ResponseBody注解和@RequestBody相似。这个注解可以放在方法上,表明返回的类型应该是可以直接写入到HTTP响应体里的(Model中不会放置该值,该值也不会被转成一个view名字)。
 
视图的深入讲解将在以后逐步讲到。
至此,一个简单的hello world就讲完了,但是我们还是有疑问,这些只是表面现象,Spring底层到底是运作的呢?
首先我们来看
<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
 系统启动时会加载DispatcherServlet这个Servlet,深入看一下:
public class DispatcherServlet extends FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware

 可见DispatcherServlet是继承了javax.servlet.http.HttpServlet的一个Servlet,既然是一个Servlet,则必然有

@Override
public final void init() throws ServletException;

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException;

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException;

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;

 这几个方法的实现,我们一个一个看:

init()方法在HttpServletBean中实现,doGet和doPost方法在FrameworkServlet中实现,doService()方法在DispatcherServlet中实现。

根据Servlet规范,一个Servlet启动的时候需要执行init()方法,一个请求来了首先判断是Get还Post方式提交,Get方式提交的执行doGet()方法,Post方式提交的执行doPost()方法。然后在doGet()和doPost()方法中可调用doService()方法,SpringMVC完全参照此规范。

在init()中,HttpServletBean执行

protected void initServletBean() throws ServletException {
	}

 讲具体的实现交给FrameworkServlt重载的方法完成

@Override
	protected final void initServletBean() throws ServletException {
System.out.println("使用servlet上下文记录日志");
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();
System.out.println("启动时间:[" + startTime + "]");

		try {
System.out.println("initWebApplicationContext()");
			this.webApplicationContext = initWebApplicationContext();
System.out.println("initFrameworkServlet(),这是一个空实现");
			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;
System.out.println("结束时间:[" + elapsedTime + "]");
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

 该方法会记录启动耗费的时间,也就时我们在启动项目时在控制台看到的时间,这里还解释了,为什么输出启动耗时的日志一般是红色的。

在该方法中执行了initWebApplicationContext()方法,进入该方法

protected WebApplicationContext initWebApplicationContext() {
//System.out.println("使用WebApplicationContextUtils工具类得到WebApplicatonContext");
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

//System.out.println("判断webApplicationContext是否为null");
		if (this.webApplicationContext != null) {
//System.out.println("webApplicationContext非空!");
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
//System.out.println("判断wac是否为null");
		if (wac == null) {
//System.out.println("was为空");
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
//System.out.println("再次判断wac是否为null");
		if (wac == null) {
System.out.println("wac还为空");
System.out.println("No context instance is defined for this servlet -> create a local one");
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

System.out.println("this.refreshEventReceived:[" + this.refreshEventReceived + "]");
		if (!this.refreshEventReceived) {
System.out.println("onRefresh");
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

System.out.println("this.publishContext:[" + this.publishContext + "]");
		if (this.publishContext) {
System.out.println("Publish the context as a servlet context attribute.");
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

 发现很长,但其实有用的只有wac = createWebApplicationContext(rootContext);在进入这个方法

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
System.out.println("createWebApplicationContext");
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
System.out.println("初始化Bean开始");
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
System.out.println("初始化Bean结束");		

		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());

System.out.println("configureAndRefreshWebApplicationContext开始");
		configureAndRefreshWebApplicationContext(wac);
System.out.println("configureAndRefreshWebApplicationContext结束");

		return wac;
	}

 于是我们就看到了有点眼熟的

ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

 这个方法就开始初始化我们servlet上下文即spring配置文件中的bean了。

我们的配置文件中servlet-context.xml只要一行

<beans:import resource="controllers.xml" />

 而controllers.xml中

<context:component-scan base-package="org.springframework.samples.mvc" />

 含义是扫描指定包下面所有带有注解的类,并将他们初始化。上面已经列出了带有@Controller注解的SimpleController类,于是Spring默认将这个bean的名字命名为simpleController(类名首字母小写),然后发现有一个方法带有@RequestMapping,Spring便通过org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping创建URL和控制器的映射(handlerMapping),具体过程可看如下日志:

DEBUG: org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Looking for URL mappings in application context: WebApplicationContext for namespace 'appServlet-servlet': startup date [Fri Mar 16 05:36:03 CST 2012]; root of context hierarchy
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'simpleController'
将一个URL路径映射到一个handler上
INFO : org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/simple] onto handler 'simpleController'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'simpleController'
将一个URL路径映射到一个handler上
INFO : org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/simple.*] onto handler 'simpleController'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'simpleController'
将一个URL路径映射到一个handler上
INFO : org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mapped URL path [/simple/] onto handler 'simpleController'

 URL到控制器的映射创建完了,剩下的便是处理器代理了(handlerAdapter),前文提到了,DispatcherServlet有一个List类型的handlerAdapters,如果为null,系统将使用默认策略。这个默认策略体现在DispatcherServlet的这个方法中

/**
	 * Initialize the HandlerAdapters used by this class.
	 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
	 * we default to SimpleControllerHandlerAdapter.
	 */
	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) {
System.out.println("确保我们有一些handlerAdapters");
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}

 这个默认策略会给我们三个HandlerAdapter,具体日志:

DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter'

至此,init()可以算是执行完了(当然为了简化,还有一些无关大局的细节略去没有讲),我们的Servlet,也就是项目就运行起来了。

下面讲解请求部分

先来看一段浏览器发出请求后,服务端的记录的日志

FrameworkServlet doGet
TRACE: org.springframework.web.servlet.DispatcherServlet - Bound request context to thread: [GET /web/simple]@902324502 org.eclipse.jetty.server.Request@35c86116
doService
DEBUG: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing GET request for [/web/simple]
doDispatch
Determine handler for the current request.
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@20d1fa4] in DispatcherServlet with name 'appServlet'
TRACE: org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - No handler mapping found for [/simple]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping@5b33a7af] in DispatcherServlet with name 'appServlet'
DEBUG: org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mapping [/simple] to HandlerExecutionChain with handler [org.springframework.samples.mvc.simple.SimpleController@573b7064] and 1 interceptor
Determine handler adapter for the current request.
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter@557ce3bb]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter@6996a298]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter@783b67a7]
Process last-modified header, if supported by the handler.
DEBUG: org.springframework.web.servlet.DispatcherServlet - Last-Modified value for [/web/simple] is: -1
Apply preHandle methods of registered interceptors.
Actually invoke the handler.
class org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
invokeHandlerMethod
真正处理
DEBUG: org.springframework.web.bind.annotation.support.HandlerMethodInvoker - Invoking request handler method: public java.lang.String org.springframework.samples.mvc.simple.SimpleController.simple()
result:[Hello world!]
getModelAndView[class java.lang.String]
原来进入了这个分支
OK搞定
具体输出数据
DEBUG: org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter - Written [Hello world!] as "text/html" using [org.springframework.http.converter.StringHttpMessageConverter@6b7de5ce]
返回mv为空,假设handler已经处理完了
Do we need view name translation?
Apply postHandle methods of registered interceptors.
DEBUG: org.springframework.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'appServlet': assuming HandlerAdapter completed request handling
Trigger after-completion for successful outcome.
[1]
TRACE: org.springframework.web.servlet.DispatcherServlet - Cleared thread-bound request context: [GET /web/simple]@902324502 org.eclipse.jetty.server.Request@35c86116
DEBUG: org.springframework.web.servlet.DispatcherServlet - Successfully completed request
TRACE: org.springframework.web.context.support.XmlWebApplicationContext - Publishing event in WebApplicationContext for namespace 'appServlet-servlet': ServletRequestHandledEvent: url=[/web/simple]; client=[0:0:0:0:0:0:0:1%0]; method=[GET]; servlet=[appServlet]; session=[null]; user=[null]; time=[154ms]; status=[OK]

 我们来详细分析一下,请求上来时,首先进入FrameworkServlet的doGet方法,然后调用

/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;

		// Expose current LocaleResolver and request as LocaleContext.
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

		// Expose current RequestAttributes to current thread.
		RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = null;
		if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
			requestAttributes = new ServletRequestAttributes(request);
			RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}

		try {
			doService(request, response);
		}
		catch (ServletException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			// Clear request attributes and reset thread-bound context.
			LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
			if (requestAttributes != null) {
				RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
				requestAttributes.requestCompleted();
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				}
				else {
					this.logger.debug("Successfully completed request");
				}
			}
			if (this.publishEvents) {
				// Whether or not we succeeded, publish an event.
				long processingTime = System.currentTimeMillis() - startTime;
				this.webApplicationContext.publishEvent(
						new ServletRequestHandledEvent(this,
								request.getRequestURI(), request.getRemoteAddr(),
								request.getMethod(), getServletConfig().getServletName(),
								WebUtils.getSessionId(request), getUsernameForRequest(request),
								processingTime, failureCause));
			}
		}
	}

 该方法很杂,其他的不要看,就看调用了doService方法即可。此doService由DispatcherServlet重写

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("doService");
		if (logger.isDebugEnabled()) {
			String requestUri = urlPathHelper.getRequestUri(request);
			logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
					" request for [" + requestUri + "]");
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			logger.debug("Taking snapshot of request attributes before include");
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		this.flashMapManager.requestStarted(request);

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		try {
			doDispatch(request, response);
		}
		finally {
			this.flashMapManager.requestCompleted(request);
			
			// Restore the original attribute snapshot, in case of an include.
			if (attributesSnapshot != null) {
				restoreAttributesAfterInclude(request, attributesSnapshot);
			}
		}
	}

 其他不要看,就看调用了doDispatch方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("doDispatch");
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		int interceptorIndex = -1;

		try {
			ModelAndView mv;
			boolean errorView = false;

			try {
				processedRequest = checkMultipart(request);

				// Determine handler for the current request.
System.out.println("Determine handler for the current request.");				
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
System.out.println("Determine handler adapter for the current request.");
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
System.out.println("Process last-modified header, if supported by the handler.");
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						String requestUri = urlPathHelper.getRequestUri(request);
						logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				// Apply preHandle methods of registered interceptors.
System.out.println("Apply preHandle methods of registered interceptors.");
				HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
				if (interceptors != null) {
					for (int i = 0; i < interceptors.length; i++) {
						HandlerInterceptor interceptor = interceptors[i];
						if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
							triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
							return;
						}
						interceptorIndex = i;
					}
				}

				// Actually invoke the handler.
System.out.println("Actually invoke the handler.");
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
System.out.println("返回mv为空,假设handler已经处理完了");

				// Do we need view name translation?
System.out.println("Do we need view name translation?");
				if (mv != null && !mv.hasView()) {
					mv.setViewName(getDefaultViewName(request));
				}

				// Apply postHandle methods of registered interceptors.
System.out.println("Apply postHandle methods of registered interceptors.");
				if (interceptors != null) {
					for (int i = interceptors.length - 1; i >= 0; i--) {
						HandlerInterceptor interceptor = interceptors[i];
						interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
					}
				}
			}
			catch (ModelAndViewDefiningException ex) {
				logger.debug("ModelAndViewDefiningException encountered", ex);
				mv = ex.getModelAndView();
			}
			catch (Exception ex) {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(processedRequest, response, handler, ex);
				errorView = (mv != null);
			}

			// Did the handler return a view to render?
			if (mv != null && !mv.wasCleared()) {
System.out.println("执行render了");
				render(mv, processedRequest, response);
				if (errorView) {
					WebUtils.clearErrorRequestAttributes(request);
				}
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
							"': assuming HandlerAdapter completed request handling");
				}
			}

			// Trigger after-completion for successful outcome.
System.out.println("Trigger after-completion for successful outcome.");
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
		}

		catch (Exception ex) {
			// Trigger after-completion for thrown exception.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		}
		catch (Error err) {
			ServletException ex = new NestedServletException("Handler processing failed", err);
			// Trigger after-completion for thrown exception.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		}

		finally {
			// Clean up any resources used by a multipart request.
			if (processedRequest != request) {
				cleanupMultipart(processedRequest);
			}
		}
	}

 这里才是最精髓的,代码

System.out.println("Determine handler for the current request.");

 对应日志部分

Determine handler for the current request.
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@20d1fa4] in DispatcherServlet with name 'appServlet'
TRACE: org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping - No handler mapping found for [/simple]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping@5b33a7af] in DispatcherServlet with name 'appServlet'
DEBUG: org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping - Mapping [/simple] to HandlerExecutionChain with handler [org.springframework.samples.mvc.simple.SimpleController@573b7064] and 1 interceptor

 代码

Determine handler adapter for the current request.

 对应日志部分

Determine handler adapter for the current request.
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter@557ce3bb]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter@6996a298]
TRACE: org.springframework.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter@783b67a7]

 handlerMapping测试了BeanNameUrlHandlerMapping发现没有,接着找DefaultAmmotationHandlerMapping,有对应的映射,搞定。

handlerAdapter测试了HttpRequestHandlerAdapter不行,SimpleControllerHandlerAdapter不行,最后找到AnnotationMethodHandlerAdapter,有对应关系,搞定。

下面来到这段代码

System.out.println("Actually invoke the handler.");
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
System.out.println("返回mv为空,假设handler已经处理完了");

 注意这里的mappedHandler.geteHandler()就是一个AnnotationMethodHandlerAdapter。

再调用

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
System.out.println(getClass());

		Class<?> clazz = ClassUtils.getUserClass(handler);
		Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
		if (annotatedWithSessionAttributes == null) {
			annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
			this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
		}

		if (annotatedWithSessionAttributes) {
			// Always prevent caching in case of session attribute management.
			checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
			// Prepare cached set of session attributes names.
		}
		else {
			// Uses configured default cacheSeconds setting.
			checkAndPrepare(request, response, true);
		}

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
System.out.println("同步");
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					return invokeHandlerMethod(request, response, handler);
				}
			}
		}

		return invokeHandlerMethod(request, response, handler);
	}

 其他的无视,看invokeHandlerMethod方法

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
System.out.println("invokeHandlerMethod");

		ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
		Method handlerMethod = methodResolver.resolveHandlerMethod(request);
		ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		ExtendedModelMap implicitModel = new BindingAwareModelMap();

		Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
System.out.println("result:[" + result + "]");
		ModelAndView mav =
				methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
		methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
		return mav;
	}

进入 getModelAndView方法

public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,
				ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {
System.out.println("getModelAndView[" + returnValue.getClass() + "]");

			ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
			if (responseStatusAnn != null) {
				HttpStatus responseStatus = responseStatusAnn.value();
				String reason = responseStatusAnn.reason();
				if (!StringUtils.hasText(reason)) {
					webRequest.getResponse().setStatus(responseStatus.value());
				}
				else {
					webRequest.getResponse().sendError(responseStatus.value(), reason);
				}

				// to be picked up by the RedirectView
				webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus);

				responseArgumentUsed = true;
			}

			// Invoke custom resolvers if present...
			if (customModelAndViewResolvers != null) {
System.out.println("Invoke custom resolvers if present...");
				for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
					ModelAndView mav = mavResolver.resolveModelAndView(
							handlerMethod, handlerType, returnValue, implicitModel, webRequest);
					if (mav != ModelAndViewResolver.UNRESOLVED) {
						return mav;
					}
				}
			}

			if (returnValue instanceof HttpEntity) {
				handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
				return null;
			}
			else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
System.out.println("原来进入了这个分支");
				handleResponseBody(returnValue, webRequest);
				return null;
			}
			else if (returnValue instanceof ModelAndView) {
				ModelAndView mav = (ModelAndView) returnValue;
				mav.getModelMap().mergeAttributes(implicitModel);
				return mav;
			}
			else if (returnValue instanceof Model) {
				return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
			}
			else if (returnValue instanceof View) {
				return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
			}
			else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {
				addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
				return new ModelAndView().addAllObjects(implicitModel);
			}
			else if (returnValue instanceof Map) {
				return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);
			}
			else if (returnValue instanceof String) {
System.out.println("返回值为String");
				return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
			}
			else if (returnValue == null) {
				// Either returned null or was 'void' return.
				if (this.responseArgumentUsed || webRequest.isNotModified()) {
					return null;
				}
				else {
					// Assuming view name translation...
					return new ModelAndView().addAllObjects(implicitModel);
				}
			}
			else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
				// Assume a single model attribute...
				addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
				return new ModelAndView().addAllObjects(implicitModel);
			}
			else {
				throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
			}
		}

 于是我们看到了熟悉的ResponseBody.class的身影

然后handleResponseBody方法

private void handleResponseBody(Object returnValue, ServletWebRequest webRequest)
				throws Exception {
System.out.println("OK搞定");
			if (returnValue == null) {
				return;
			}
			HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
			HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
			writeWithMessageConverters(returnValue, inputMessage, outputMessage);
		}

 可以看到就是在这个方法中直接将字符串发送到浏览器的。writeWithMessageConverters方法无关紧要就不要列出来了。

呼,我是为了完整性,才将方法整个拷贝下来了,其实我们只需要关注主要的一行调用代码即可。

总结一下?

我觉得原理知道即可,关键还是会用,会搭建。下面讲一下如何搭建这个sample。

老办法,从GitHub上将代码clone下来加载到IDE中。

git clone git://github.com/stephansun/samples.git

最后补充一下pom.xml

<dependencies>
  	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>3.1.0.RELEASE</version>
	</dependency>
  </dependencies>

  仅仅加载了spring-webmvc,打开m2e的Dependency Hierarchy栏,观察spring模块间的依赖关系:

spring-webmvc

|- spring-asm

|- spring-beans

|- spring-context

|- spring-context-support

|- spring-core

|- spring-expression

|- spring-web

spring-beans

|- spring-core

spring-context

|- spring-aop

|- spring-beans

|- spring-core

|- spring-expression

|- spring-asm

spring-context-support

|- spring-beans

|- spring-context

|- spring-core

spring-core

|- spring-asm

|- commons-logging

spring-expression

|- spring-core

spring-web

|- aoplliance

|- spring-beans

|- spring-context

|- spring-core

Spring发布包

  • spring-aop.jar:此JAR文件包含了所有你在应用中使用Spring AOP特性时需要的类。如果应用中使用了其他涉及AOP的Spring功能时,例如声明式事务管理,你也需要将此JAR文件包含进来。
  • spring-beans.jar:此文件包含了所有Spring依赖注入需要的代码。包括bean工厂和相关支持类。大部分情况下,你会需要加入spring-context.jar文件,它包含了建立应用环境上下文需要的代码
  • spring-context.jar:此JAR文件包含了建立Spring应用环境上下文所需的代码,它将主要的ApplicationContext接口和实现、说明、JNDI、调度、主题和验证一起纳入其中
  • spring-context-support.jar:这个包文件包括了Spring的工具代码,其中包括说明、电子邮件、调度支持以及脚本语言支持
  • spring-core.jar:此文件包含了Spring框架的核心代码。它用来处理注解、枚举、任务执行、资源加载以及其他一些即便在Spring框架环境外也会有用的工具和异常类。
  • spring-jdbc.jar:此文件包含了JDBC支持类的代码,例如JdbcTemplate类和JdbcDaoSupport类
  • spring-jms.jar:此文件包含jms的代码
  • spring-orm.jar:此文件包含了对象-关系映射(ORM)工具需要的文件。把这个包加入到classpath上将会给你提供针对Hibernate3,iBatis,JDO,JPA和TopLink的Spring支持
  • spring-test.jar:此文件包含了使用Spring框架编写单元测试和集成测试的支持代码
  • spring-tx.jar:此文件提供了核心的数据访问异常和事务技术支持。这两个概念彼此关系密切,因为一般情况下事务总同某些数据访问代码一起工作的
  • spring-web.jar:此文件包含了Spring Web支持(工具类,绑定器,分段文件解析器)的代码
  • spring-webmvc.jar:此文件包含了Spring MVC的代码
(2012.3.16 7:21)
 

相关推荐