在詳解SpringMVC處理流程之前渣淤,首先我們要做好準備工作,比如初始化SpringMVC容器吉嫩,如果SpringMVC和SpringMVC集成話价认,同樣也需要初始化Spring容器。
容器初始化
web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext-*.xml</param-value>
</context-param>
<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*:dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
從web.xml中可以看出通過ContextLoaderListener初始化Spring容器自娩,通過DispatcherServlet初始化SpringMVC容器用踩,SpringMVC容器作為Spring容器的子容器設(shè)置在Spring容器。
ContextLoaderListener的作用
初始ApplicationContext(默認的是XmlWebApplicationContext)然后將其放在ServletContext中。
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
ServletContext作用
每一個web應(yīng)用都有一個 ServletContext與之相關(guān)聯(lián)捶箱。
ServletContext對象在應(yīng)用啟動的被創(chuàng)建智什,在應(yīng)用關(guān)閉的時候被銷毀。
ServletContext在全局范圍內(nèi)有效丁屎,類似于應(yīng)用中的一個全局變量荠锭。
DispatcherServlet作用
DispatcherServlet類圖
通過類圖可以看出,DispatcherServlet繼承了FrameworkServlet和HttpServletBean晨川。
HttpServletBean作用
HttpServletBean的作用主要是做一些初始化证九,將web.xml中配置的參數(shù)設(shè)置到Servlet中
//比如初始化init-param中的參數(shù)
<init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:dispatcher-servlet.xml</param-value> </init-param>
//源碼片段
HttpServletBean.ServletConfigPropertyValues ex = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ServletContextResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(ex, true);
FrameworkServlet作用
FrameworkServlet的作用講Servlet和Spring容器關(guān)聯(lián)。其實也就是初始化FrameworkServlet的屬性webApplicationContext共虑,這個屬性代表SpringMVC上下文愧怜,它有個父類上下文,既web.xml中配置的ContextLoaderListener監(jiān)聽器初始化的容器上下文妈拌。
//源碼片段
protected WebApplicationContext initWebApplicationContext() {
//這個設(shè)置springMVC的父類上下文為ContextLoaderListener初始化的容器上下文
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if(this.webApplicationContext != null) {
wac = this.webApplicationContext;
if(wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext attrName = (ConfigurableWebApplicationContext)wac;
if(!attrName.isActive()) {
if(attrName.getParent() == null) {
attrName.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(attrName);
}
}
}
if(wac == null) {
wac = this.findWebApplicationContext();//一般返回的都是null
//具體實現(xiàn)拥坛,獲取DispatcherServlet的applicationContext
//WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName);
}
if(wac == null) {
wac = this.createWebApplicationContext(rootContext);
}
if(!this.refreshEventReceived) {
this.onRefresh(wac);
}
if(this.publishContext) {
//attrName1=org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher
String attrName1 = this.getServletContextAttributeName();
//新創(chuàng)建的容器上下文設(shè)置到ServletContext中
this.getServletContext().setAttribute(attrName1, wac);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet \'" + this.getServletName() + "\' as ServletContext attribute with name [" + attrName1 + "]");
}
}
return wac;
}
DispatcherServlet主要組建
DispatcherServlet覆寫了FrameworkServlet中的onRefresh()方法,onRefresh()方法是鉤子方法尘分,子類可以重寫自己特有的方法猜惋。
//初始化DispatcherServlet使用的策略
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
SpringMVC處理流程
簡單的Demo
<!-- dispatcher-servlet.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- HandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 處理器 -->
<bean name="/hello" class="HelloWorldController">
</beans>
<!-- HelloWorldController -->
public class HelloWorldController implements Controller {
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//1、收集參數(shù)培愁、驗證參數(shù)
//2著摔、綁定參數(shù)到命令對象
//3、將命令對象傳入業(yè)務(wù)對象進行業(yè)務(wù)處理
//4定续、選擇下一個頁面
ModelAndView mv = new ModelAndView();
//添加模型數(shù)據(jù) 可以是任意的POJO對象
mv.addObject("message", "Hello World!");
//設(shè)置邏輯視圖名谍咆,視圖解析器會根據(jù)該名字解析到具體的視圖頁面
mv.setViewName("hello");
return mv;
}
}
處理流程
處理流程圖
用戶將發(fā)送請求至前端控制器DispatcherServlet
DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。
處理器映射器找到具體的處理器私股,生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet摹察。
DispatcherServlet調(diào)用HandlerAdapter處理器適配器
HandlerAdapter經(jīng)過適配調(diào)用具體的處理器(Controller,也叫后端控制器)庇茫。
Controller執(zhí)行完成返回ModelAndView
HandlerAdapter將controller執(zhí)行結(jié)果ModelAndView返回給DispatcherServlet
DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
ViewReslover解析后返回具體View
DispatcherServlet根據(jù)View進行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)港粱。
DispatcherServlet響應(yīng)用戶
DispatcherServlet源碼
DispatcherServlet中最主要的核心功能是由doService()和doDispatch()實現(xiàn),接下來看一下他們的源碼
//doService()
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() +
"' processing 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 attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
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));
}
}
}
// 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 {
// 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 {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
// 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 = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
ModelAndView mv = null;
boolean errorView = false;
try {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 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.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// 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()) {
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.
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);
}
// Reset thread-bound context.
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
// Clear request attributes.
requestAttributes.requestCompleted();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
}
}
再說DispatcherServlet
- 從上面的處理流程可以看出DispatcherServlet主要負責流程的控制旦签,它的主要職責如下:
- 文件上傳解析查坪,如果請求類型是multipart將通過MultipartResolver進行文件上傳解析;
通過HandlerMapping宁炫,將請求映射到處理器(返回一個HandlerExecutionChain偿曙,它包括一個處理器、多個HandlerInterceptor攔截器)羔巢;
通過HandlerAdapter支持多種類型的處理器(HandlerExecutionChain中的處理器)望忆;
通過ViewResolver解析邏輯視圖名到具體視圖實現(xiàn)罩阵;
本地化解析;
渲染具體的視圖等启摄;
如果執(zhí)行過程中遇到異常將交給HandlerExceptionResolver來解析稿壁。
DispatcherServlet特殊中的Bean:
- Controller:處理器/頁面控制器,做的是MVC中的C的事情歉备,但控制邏輯轉(zhuǎn)移到前端控制器了傅是,用于對請求進行處理;
HandlerMapping:請求到處理器的映射蕾羊,如果映射成功返回一個HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象喧笔、多個HandlerInterceptor攔截器)對象;如BeanNameUrlHandlerMapping將URL與Bean名字映射龟再,映射成功的Bean就是此處的處理器书闸;
HandlerAdapter:HandlerAdapter將會把處理器包裝為適配器,從而支持多種類型的處理器利凑,即適配器設(shè)計模式的應(yīng)用浆劲,從而很容易支持很多類型的處理器;如SimpleControllerHandlerAdapter將對實現(xiàn)了Controller接口的Bean進行適配哀澈,并且diao處理器的handleRequest方法進行功能處理梳侨;
ViewResolver:ViewResolver將把邏輯視圖名解析為具體的View,通過這種策略模式日丹,很容易更換其他視圖技術(shù);如InternalResourceViewResolver將邏輯視圖名映射為jsp視圖蚯嫌;
LocalResover:本地化解析哲虾,因為Spring支持國際化,因此LocalResover解析客戶端的Locale信息從而方便進行國際化择示;
ThemeResovler:主題解析束凑,通過它來實現(xiàn)一個頁面多套風格,即常見的類似于軟件皮膚效果栅盲;
MultipartResolver:文件上傳解析汪诉,用于支持文件上傳;
HandlerExceptionResolver:處理器異常解析谈秫,可以將異常映射到相應(yīng)的統(tǒng)一錯誤界面扒寄,從而顯示用戶友好的界面(而不是給用戶看到具體的錯誤信息);
RequestToViewNameTranslator:當處理器沒有返回邏輯視圖名等相關(guān)信息時拟烫,自動將請求URL映射為邏輯視圖名该编;
FlashMapManager:用于管理FlashMap的策略接口,F(xiàn)lashMap用于存儲一個請求的輸出硕淑,當進入另一個請求時作為該請求的輸入课竣,通常用于重定向場景嘉赎,后邊會細述。
-
攔截器的處理流程
攔截器