SpringMVC原始碼分析2:SpringMVC設計理念與DispatcherServlet

a724888發表於2019-08-25

SpringMVC簡介

SpringMVC作為Struts2之後異軍突起的一個表現層框架,正越來越流行,相信javaee的開發者們就算沒使用過SpringMVC,也應該對其略有耳聞。我試圖透過對SpringMVC的設計思想和原始碼實現的剖析,從抽象意義上的設計層面和實現意義上的程式碼層面兩個方面,逐一揭開SpringMVC神秘的面紗,本文的程式碼,都是基於Spring的 3.1.3RELEASE版本。

任何一個框架,都有自己特定的適用領域,框架的設計和實現,必定是為了應付該領域內許多通用的,煩瑣的、基礎的工作而生。SpringMVC作為一個表現層框架,也必須直面Web開發領域中表現層中的幾大課題,並給出自己的回答:

  • URL到框架的對映。
  • http請求引數繫結
  • http響應的生成和輸出

這三大課題,組成一個完整的web請求流程,每一個部分都具有非常廣闊的外延。SpringMVC框架對這些課題的回答又是什麼呢?

學習一個框架,首要的是要先領會它的設計思想。從抽象、從全域性上來審視這個框架。其中最具有參考價值的,就是這個框架所定義的核心介面。核心介面定義了框架的骨架,也在最抽象的意義上表達了框架的設計思想。

下面我以一個web請求流程為載體,依次介紹SpringMVC的核心介面和類。

使用者在瀏覽器中,輸入了的地址,回車後,瀏覽器發起一個http請求。請求到達你的伺服器後,首先會被SpringMVC註冊在web.xml中的前端轉發器DispatcherServlet接收,DispatcherServlet是一個標準的Servlet,它的作用是接受和轉發web請求到內部框架處理單元。

HandlerMapping介面

下面看一下第一個出現在你面前的核心介面,它是在org.springframework.web.servlet包中定義的HandlerMapping介面:

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
public interface HandlerMapping {
	String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
	String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
	String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
	String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
	String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

為了閱讀方便,我去掉了原始碼中的註釋,但是我強烈建議你一定要記得去閱讀它,這樣你才能從框架的設計者口中得到最準確的關於這個類或者介面的設計說明。類中定義的幾個常量,我們先不去管它。關鍵在於這個介面中唯一的方法:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

這個方法就算對於一個java初學者來說,也很容易理解:它只有一個型別為HttpServletRequest的引數,throws Exception的宣告表示它不處理任何型別的異常,HandlerExecutionChain是它的返回型別。

DispatcherServlet接受請求並找到對應Handler

回到DispatcherServlet的處理流程,當DispatcherServlet接收到web請求後,由標準Servlet類處理方法doGet或者doPost,經過幾次轉發後,最終註冊在DispatcherServlet類中的HandlerMapping實現類組成的一個List(有點拗口)會在一個迴圈中被遍歷。以該web請求的HttpServletRequest物件為引數,依次呼叫其getHandler方法,第一個不為null的呼叫結果,將被返回。DispatcherServlet類中的這個遍歷方法不長,貼一下,讓大家有更直觀的瞭解。

/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or <code>null</code> 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;
	}

是的,第一步處理就這麼簡單的完成了。一個web請求經過處理後,會得到一個HandlerExecutionChain物件,這就是SpringMVC對URl對映給出的回答。需要留意的是,HandlerMapping介面的getHandler方法引數是HttpServletRequest,這意味著,HandlerMapping的實現類可以利用HttpServletRequest中的 所有資訊來做出這個HandlerExecutionChain物件的生成”決策“。這包括,請求頭、url路徑、cookie、session、引數等等一切你從一個web請求中可以得到的任何東西(最常用的是url路徑)。

SpirngMVC的第一個擴充套件點,就出現在這裡。我們可以編寫任意的HandlerMapping實現類,依據任何策略來決定一個web請求到HandlerExecutionChain物件的生成。可以說,從第一個核心介面的宣告開始,SpringMVC就把自己的靈活性和野心暴露無疑:哥玩的就是”Open-Closed“。

HandlerExecutionChain這個類,就是我們下一個要了解的核心類。從名字可以直觀的看得出,這個物件是一個執行鏈的封裝。熟悉Struts2的都知道,Action物件也是被層層攔截器包裝,這裡可以做個類比,說明SpringMVC確實是吸收了Struts2的部分設計思想。

HandlerExecutionChain類的程式碼不長,它定義在org.springframework.web.servlet包中,為了更直觀的理解,先上程式碼。

package org.springframework.web.servlet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.CollectionUtils;
public class HandlerExecutionChain {
	private final Object handler;
	private HandlerInterceptor[] interceptors;
	private List<HandlerInterceptor> interceptorList;
	public HandlerExecutionChain(Object handler) {
		this(handler, null);
	}
	public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
		if (handler instanceof HandlerExecutionChain) {
			HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
			this.handler = originalChain.getHandler();
			this.interceptorList = new ArrayList<HandlerInterceptor>();
			CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
			CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
		}
		else {
			this.handler = handler;
			this.interceptors = interceptors;
		}
	}
	public Object getHandler() {
		return this.handler;
	}
	public void addInterceptor(HandlerInterceptor interceptor) {
		initInterceptorList();
		this.interceptorList.add(interceptor);
	}
	public void addInterceptors(HandlerInterceptor[] interceptors) {
		if (interceptors != null) {
			initInterceptorList();
			this.interceptorList.addAll(Arrays.asList(interceptors));
		}
	}
	private void initInterceptorList() {
		if (this.interceptorList == null) {
			this.interceptorList = new ArrayList<HandlerInterceptor>();
		}
		if (this.interceptors != null) {
			this.interceptorList.addAll(Arrays.asList(this.interceptors));
			this.interceptors = null;
		}
	}
	public HandlerInterceptor[] getInterceptors() {
		if (this.interceptors == null && this.interceptorList != null) {
			this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
		}
		return this.interceptors;
	}
	@Override
	public String toString() {
		if (this.handler == null) {
			return "HandlerExecutionChain with no handler";
		}
		StringBuilder sb = new StringBuilder();
		sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
		if (!CollectionUtils.isEmpty(this.interceptorList)) {
			sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
			if (this.interceptorList.size() > 1) {
				sb.append("s");
			}
		}
		return sb.toString();
	}
}

亂七八糟一大堆,相信你也沒全看完,也沒必要全看。其實只需要看兩行足矣。

private final Object handler;
	private HandlerInterceptor[] interceptors;

不出我們所料,一個實質執行物件,還有一堆攔截器。這不就是Struts2中的實現麼,SpringMVC沒有避嫌,還是採用了這種封裝。得到HandlerExecutionChain這個執行鏈(execution chain)之後,下一步的處理將圍繞其展開。

HandlerInterceptor介面

HandlerInterceptor也是SpringMVC的核心介面,定義如下:

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerInterceptor {
	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
	    throws Exception;
	void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception;
	void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception;
}

至此,HandlerExecutionChain整個執行脈絡也就清楚了:在真正呼叫其handler物件前,HandlerInterceptor介面實現類組成的陣列將會被遍歷,其preHandle方法會被依次呼叫,然後真正的handler物件將被呼叫。

handler物件被呼叫後,就生成了需要的響應資料,在將處理結果寫到HttpServletResponse物件之前(SpringMVC稱為渲染檢視),其postHandle方法會被依次呼叫。檢視渲染完成後,最後afterCompletion方法會被依次呼叫,整個web請求的處理過程就結束了。

在一個處理物件執行之前,之後利用攔截器做文章,這已經成為一種經典的框架設計套路。Struts2中的攔截器會做諸如引數繫結這類複雜的工作,那麼SpringMVC的攔截器具體做些什麼呢?我們暫且不關心,雖然這是很重要的細節,但細節畢竟是細節,我們先來理解更重要的東西。

HandlerInterceptor,是SpringMVC的第二個擴充套件點的暴露,透過自定義攔截器,我們可以在一個請求被真正處理之前、請求被處理但還沒輸出到響應中、請求已經被輸出到響應中之後這三個時間點去做任何我們想要做的事情。Struts2框架的成功,就是源於這種攔截器的設計,SpringMVC吸收了這種設計思想,並推陳出新,更合理的劃分了三個不同的時間點,從而給web請求處理這個流程,提供了更大的擴充套件性。

這個HandlerExecutionChain類中以Object引用所宣告的handler物件,到底是個什麼東東?它是怎麼被呼叫的?

HandlerAdapter

回答這些問題之前,先看SpringMVC中的又一個核心介面,HandlerAdapter:

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerAdapter {
	boolean supports(Object handler); 
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);
}

在DispatcherServlet中,除了HandlerMapping實現類的列表,同樣也註冊了一個HandlerAdapter實現類組成的列表,有程式碼為證。

/** List of HandlerMappings used by this servlet */
	private List<HandlerMapping> handlerMappings;
	/** List of HandlerAdapters used by this servlet */
	private List<HandlerAdapter> handlerAdapters;

接下來,我們再以DispatcherServlet類中另外一段程式碼來回答上述的問題:

/**
	 * 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 +
				"]: Does your handler implement a supported interface like Controller?");
	}

請求流程總結

這段程式碼已經很明顯了,HandlerExecutionChain中的handler物件會被作為引數傳遞進去,在DispatcherServlet類中註冊的HandlerAdapter實現類列表會被遍歷,然後返回第一個supports方法返回true的HandlerAdapter物件,用這個HandlerAdapter實現類中的handle方法處理handler物件,並返回ModelAndView這個包含了檢視和資料的物件。HandlerAdapter就是SpringMVC提供的第三個擴充套件點,你可以提供自己的實現類來處理handler物件。

ModelAndView物件的程式碼就不貼了,它是SpringMVC中對檢視和資料的一個聚合類。其中的檢視,就是由SpringMVC的最後一個核心介面View所抽象:

package org.springframework.web.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface View {
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
	String getContentType();
	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

所有的資料,最後會作為一個Map物件傳遞到View實現類中的render方法,呼叫這個render方法,就完成了檢視到響應的渲染。這個View實現類,就是來自HandlerAdapter中的handle方法的返回結果。當然從ModelAndView到真正的View實現類有一個解析的過程,ModelAndView中可以有真正的檢視物件,也可以只是有一個檢視的名字,SpringMVC會負責將檢視名稱解析為真正的檢視物件。

至此,我們瞭解了一個典型的完整的web請求在SpringMVC中的處理過程和其中涉及到的核心類和介面。

在一個典型的SpringMVC呼叫中,HandlerExecutionChain中封裝handler物件就是用@Controller註解標識的類的一個例項,根據類級別和方法級別的@RequestMapping註解,由預設註冊的DefaultAnnotationHandlerMapping(3.1.3中更新為RequestMappingHandlerMapping類,但是為了向後相容,DefaultAnnotationHandlerMapping也可以使用)生成HandlerExecutionChain物件,再由AnnotationMethodHandlerAdapter(3.1.3中更新為RequestMappingHandlerAdapter類,但是為了向後相容,AnnotationMethodHandlerAdapter也可以使用)來執行這個HandlerExecutionChain物件,生成最終的ModelAndView物件後,再由具體的View物件的render方法渲染檢視。

可以看到,作為一個表現層框架,SpringMVC沒有像Struts2那樣激進,並沒有採用和Web容器完全解耦的設計思想,而是以原生的Servlet框架物件為依託,透過合理的抽象,制定了嚴謹的的處理流程。這樣做的結果是,執行效率比Struts2要高,靈活性也上升了一個層次。

圖片描述


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69906029/viewspace-2654872/,如需轉載,請註明出處,否則將追究法律責任。

相關文章