基于Spring MVC的Web应用开发(1) - HelloWorld
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;@Controllerpublic class SimpleController {@RequestMapping("/simple")@ResponseBodypublic 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,则必然有
?
@Overridepublic final void init() throws ServletException;@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException;@Overrideprotected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException;@Overrideprotected 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重载的方法完成
?
@Overrideprotected 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 itwac = 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, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.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 idwac = 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 onewac = 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 hierarchyDEBUG: 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 doGetTRACE: org.springframework.web.servlet.DispatcherServlet - Bound request context to thread: [GET /web/simple]@902324502 org.eclipse.jetty.server.Request@35c86116doServiceDEBUG: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'appServlet' processing GET request for [/web/simple]doDispatchDetermine 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 interceptorDetermine 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: -1Apply preHandle methods of registered interceptors.Actually invoke the handler.class org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterinvokeHandlerMethod真正处理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 handlingTrigger 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@35c86116DEBUG: org.springframework.web.servlet.DispatcherServlet - Successfully completed requestTRACE: 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重写
?
@Overrideprotected 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 RedirectViewwebRequest.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)?
?
?
?
?
?
?
?
?
?
?
?
?
?
??
?