SpringMVC 请求处理流程

6 篇文章 0 订阅


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响应内容。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值