1. demo代码清单
清单一(web.xml中Servlet配置):
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Web Application Template</display-name>
<!-- 设置Log4j: -->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>webapp.root</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- 引入Mybatis配置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml, classpath:spring-mybatis.xml</param-value>
</context-param>
<listener>
<!-- 需放在LogjConfigListener后面 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 将POST转为:POST/DELETE/PUT: -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<!-- 为Filter分配流量 -->
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 设置DispactherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvcContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 为dispatherServlet分配请求 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
清单二(
mvcContext.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 扫描项目所有的包:扫描并自动装载@compoent、@controller、@repository等 -->
<context:component-scan base-package="com.nerbit.springmvc.template"></context:component-scan>
<!--
1. 自动添加:
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
RequestMappingHandlerAdapter
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver等
2. 注册自定义的converter-service
-->
<mvc:annotation-driven conversion-service="convertionService"></mvc:annotation-driven>
<!-- ViewResolver: -->
<bean
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- FTL模版目录 -->
<property name="templateLoaderPath" value="/WEB-INF/view/"></property>
<property name="freemarkerSettings">
<props>
<prop key="template_update_delay">0</prop>
<prop key="default_encoding">UTF-8</prop>
<prop key="locale">zh_CN</prop>
<prop key="url_escaping_charset">UTF-8</prop>
<prop key="whitespace_stripping">true</prop>
<prop key="number_format">0.##########</prop>
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
</props>
</property>
</bean>
<bean id="freemarkerViewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="contentType" value="text/html; charset=UTF-8"></property>
<property name="cache" value="true"></property>
<property name="prefix" value=""></property>
<property name="suffix" value=".ftl"></property>
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="allowSessionOverride" value="true" />
<property name="requestContextAttribute" value="request" />
</bean>
<!-- multipart requests:multipart文件上传、mutipart表单请求
前端用<input type="file" name="xxx"/>上传,
Controller接口方法中用@RequestParam(value="xxx")MultipartFile file来接收
-->
<bean id="commonsMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="4000000"></property>
<property name="maxInMemorySize" value="256000"></property>
</bean>
<!--
default-servlet-handler将在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler,
它会对进入DispatcherServlet的请求进行筛选,如果是没有经过映射的请求,就将该请求交由WEB应用服务器默认的
Servlet处理,如果Web服务器的默认Servlet名称不是default,则需要通过default-servlet-handler-name属性显式指定
-->
<mvc:default-servlet-handler/>
<!-- 配置conversionService,将自定义的Converter列入 -->
<bean id="convertionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="string2UserConverter"></ref>
</set>
</property>
</bean>
</beans>
利用
FreeMarkerViewResolver处理View
清单三(HelloController.java):
@Controller
public class HelloController {
@RequestMapping(value="/hello")
public String hello() {
System.out.println("hello....");
return "hello";
}
}
2. 跟踪请求处理过程
在
DispatcherServlet代码的doService方法入口打上断点
本地Debug运行该web项目,在浏览器访问localhost:8080/hello,程序停在了
DispatcherServlet代码的doService方法入口。
首先代码进入DispatcherServlet的
doService
(HttpServletRequest
request
,
HttpServletResponse
response
)方法:
干了一点琐碎的屁事后,走到下一个关键点:
又调用了
DispatcherServlet自己的另一个方法
doDispatch
(
HttpServletRequest
request
,
HttpServletResponse
response
)
然后进入doDispatch下一关键点:
调用
DispatcherServlet自己的getHandler方法得到HandlerExecutionChain(内含用于请求处理的处理器handler以及处理器拦截器handler interceptors)类型的mappedHandler,
具体获取方式,见getHandler代码:
遍历DispatcherServlet的List<HandlerMapping>类型的handlerMappings,调用每个HandlerMapping(HandlerMapping只是一个接口,这里实际用到的是其子类RequestMappingHandlerMapping,
用于映射requests到handler objects
)的getHandler。
getHandler(request)方法根据request查找预先构建好的handlerExecutionChain(处理request的handler、静态与动态的interceptors)。
得到mappedHandler后,进入下一关键点:
通过DispatcherServl自己的方法
getHandlerAdapter
(
Object
handler
)得到HandlerAdapter()型的对象ha:
遍历DispatcherServlet 的List<HandlerAdapters>类型(HandlerAdapter只是一个接口,这里实际用到的是RequestMappingHandlerAdapter)的handlerAdapters,找到一个支持当前mappedHandler(HandlerExecutionChain类型)中的handler(Object类型)的
HandlerAdapter。
然后,走到下一个关键点:
调用mappedHandler(HandlerExecutionChain类型)的applyPreHandle方法,该方法封装了对拦截器(interceptors)的preHandle方法的调用逻辑。
注意到,如果applyHandle返回看false说明某个拦截器要求忽略掉其后续拦截器和处理器。这时doDispatcher反法立刻return。
下面进入到applyPreHandle:
从该方法可以看到拦截器interceptors的preHandle方法及特殊情况下afterCompletion方法的时序。
顺序遍历每个拦截器interceptor,调用其preHandle方法,并记录最后一次调用的interceptorIndex。如果其中某个拦截器的preHandle方法返回了false,后继拦截器的preHandle方法将会被忽略过,并且会紧接着调用mappedHandler(HandlerExecutionChain类型)的triggerAfterCompletion(该方法负责协调interceptors的afterCompletion方法的调用)。如果某个interceptor返回了fasle,applyPreHandle最后也会返回false以通知mappedHandler的mappedHandler方法。下面是triggerAfterCompletion代码:
对于所有已经被调用了preHandler的interceptors,逆序调用其afterCompletion方法。
applHandle方法返回后,到达下一个关键点:
这是最关键的一行。调用ha(HandlerAdapter类型)对象的handle方法,将request、response、mappedHandler中的handler对象传入,
得到ModelAndView类型的mv对象。
HandlerAdapter是一个接口,这里实际用到的是其实现类RequestMappingHandlerAdapter,下面看一下它对handle方法的实现:
RequestMappingHandlerAdapter的handle方法继承自
AbstractHandlerMethodAdapter,它把传进来的handler对象视为一个HanderMethod,并且它啥也不干直接调用
抽象方法handleInternal。下面是RequestMappingHandlerAdapter对handleInternal的实现的关键代码:
调用RequestMappingHandlerAdapter的invokeHandleMethod方法。如果request的session非null的话要进行同步。
搞了半天,终于要真正的按照web开发者的代码来处理请求了:
通过调用handlerMethod将handlerMethod结果包装成一个ModelAndView并将其返回。
下面到来DispatcherServlet.doDispatcher中的下一个关键点:
applyDefaultViewName方法实现如下:
applyDefaultViewName方法通过调用mv(ModelAndView)的setViewName来设置其view属性设置为getDefaultViewName方法的返回值。
getDefaultViewName方法如下:
该方法通过DispatcherServlet持有的viewNameTranslator(
RequestToViewNameTranslator类型,这里的实际类型为其实现类Default
RequestToViewNameTranslator)
工具从request中得到视图名,其中的逻辑大概为:prefix+regulate(request.getPath) + suffix。
然后,进入下一个关键点:
调用mappedHandler(HandlerExecutionChain)的applyPostHandle:
逆序调用所有拦截器的postHandle方法。
前面所讲的DispatcherServlet中的这些事情(从mappedHandler=.....开始)干完之后,进入到下一个达的关键点:
DispatcherServlet的processDispatcherResult方法主要包括三部分逻辑:
1. 处理dispatcherException:
2. 渲染
主要是调用DispatcherServlet的render方法来渲染视图。render方法的逻辑又可分为两部分:
2.1
如果mv(ModelAndView)是一个reference(mv.isReference==true),即mv中的view并不是最终的view对象,需要利用ViewResolver进行解析(见resolveViewName方法)得到最终的View;
如果mv不是一个reference,则其中的view即为最终的view对象,不需要再用ViewResolver解析得到:
下面是用来解析reference类型的mv的DispatcherServlet.resolveViewName方法:
还是这种逻辑,遍历DispatcherServlet的viewResolvers(List <ViewResolver>)直到遇见一个可以解析该viewName的解析器,然后返回其解析结果。
根据前边web.xml配置可知,这里只有一个FreeMarkerViewResolver,解析得到FreeMarkerView类型的view。
至于静态资源的解析,由于其HandlerAdapter的handle方法返回的ModelAndView对象为null,DispacherServlet.render方法不会被执行。
2.2
调用View的render方法,将view和model渲染成response响应内容:
到此,从request到response的正常请求处理已经完成了。
3. triggerAfterCompletion:
mappedHandler(HandlerExecutionChain)的triggerAfterCompletion方法如下:
根据interceptorIndex逆序调用在preHandle中个返回false的拦截器之前的所有拦截器的afterCompletion方法。这个在前面讲到过了。
afterCompletion是要传入response参数的,
因此,该方法有机会改变response响应内容。