文章目錄
探索SpringMVC的祕密
我們都知道在Springmvc中前端控制器,是執行的核心控制器,從繼承關係上看其實DispatcherServlet就是一個servlet。這時候我們回顧一下DispatcherServlet是怎麼配置的?是不是在
<servlet></servlet>
裏面配置的,我們剛學習servlet的時候是不是自定義servlet也是在web.xml中通過<servlet></servlet>
配置的呢?答案都是yes,下面的圖瞭解一下,就繼續看下面的內容…
我們學過javaweb部分都知道servlet的生命週期中有一個service方法是提供服務的方法,每次訪問servlet的時候,service方法就會被調用一次。
在FrameworkServlet中有service方法,源碼如下:
重點關注:
processRequest(request, response);
super.service(request, response);
/**
* Override the parent class implementation in order to intercept PATCH
* requests.
* 重寫父類實現以攔截PATCH請求。
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getMethod();
if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
processRequest(request, response);
}
else {
//
super.service(request, response);
}
}
這裏做了 一下請求方式的判斷如果請求的方式是下面這幾種的話調用processRequest()
方法,否則調用父類的service方法,即HttpServlet的service
方法。
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
這裏我們進入processRequest()
方法,源碼如下:
處理此請求,發佈事件,無論結果如何。
實際的事件處理由抽象的
doService模板方法
執行,所以我們只需要關注其中調用的doService()
方法,其他代碼一笑而過...
/**
* 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;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
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 {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, startTime, failureCause);
}
}
點擊doService()
方法之後,我們發現它在 FrameworkServlet
中是一個抽象的方法,所以它的子類一定會實現它,我們回到DispatcherServlet
中尋找doService()
方法,其源代碼如下:
公開特定於DispatcherServlet的請求屬性,並將其委託給
doDispatch()
進行實際的調度,同上我們只需要找到doDispatch()
方法,其他代碼一笑而過....
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link *#doDispatch} for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" 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));
}
}
}
// 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());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
finally {
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
doDispatch()
方法
當我們進入doDispatch()
方法,下面好戲就開始了…
下面的內容是翻譯自
doDispatch()
方法的註釋:
- 將實際的分派處理程序處理。
- 該處理程序將通過依次應用servlet的處理程序映射來獲得。
HandlerAdapter將通過查詢servlet已安裝的HandlerAdapter來找到第一個支持handler類的HandlerAdapter。
所有HTTP方法都由此方法處理。
- 由處理器適配器或處理器本身來決定哪些方法是可接受的。
特別是第3點,是促使我探究源碼的原因,因爲這裏使用了設計模式中適配器模式的思想實現的。
下面將對這個方法進行詳細的解釋:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// Determine handler for the current request.
// 確定當前請求的處理程序。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
// 實際調用處理程序。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
確定當前請求的處理程序getHandler
mappedHandler = getHandler(processedRequest);
我們進入到
DispatcherServlet的getHandler()
方法,源碼如下:
- 返回此請求的
HandlerExecutionChain即處理程序執行鏈,由處理程序對象和任何處理程序攔截器組成。
- 依次嘗試所有處理程序映射。
- 進入到此方法中就拿到
this.handlerMappings
,而this.handlerMappings
是此Servlet使用的HandlerMapping列表。- 遍歷
this.handlerMappings
拿到處理程序執行鏈
,我測試使用的控制器是使用註解@Controller
進行配置的,所以通過RequestMappingHandlerMapping
就拿到了處理程序執行鏈。當然下面那個也是有用的,具體看我們的控制器是有什麼方式配置的。
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
返回結果爲:處理程序執行鏈HandlerExecutionChain
。
確定當前請求的處理程序適配器getHandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
返回此處理程序對象的HandlerAdapter。根據不同的handler獲取不同的handlerAdapter。
結果返回的是:
RequestMappingHandlerAdapter
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
通過拿到的RequestMappingHandlerAdapter
去執行handler,即去執行控制器也就是我配置的HelloWorldController
實際調用處理程序
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
到了這一步我們就得到了ModelAndView對象,然後根據ModelAndView對象去找對應的視圖解析器去解析視圖,返回邏輯視圖,然後渲染視圖返回就可以了。
小結
如果你跟着上面的思路,並熟悉配置Controller,ModelAndView,視圖解析器,設計模式-適配器模式的話再結合着下面這張圖,我相信你會非常通透。
附:
DispatcherServlet.properties
兩種HandlerMapping
BeanNameUrlHandlerMapping
DefaultAnnotationHandlerMapping
三種HandlerAdapter
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter
AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
定義的Controller
調試的過程中斷點大多在都
doDispatch()
方法中。
/**
* @Date 2020/6/16 17:22
* @Version 10.21
* @Author DuanChaojie
*/
@Controller
@RequestMapping("/user")
public class HelloWorldController {
@RequestMapping(value = "/justweb" )
public String hello(){
System.out.println("使用註解配置的Controller,訪問成功了...\n\r開始源碼分析....");
return "success";
}
}