前言
上一節(jié)我們看到了SpringMVC在初始化的時候做了大量的準(zhǔn)備工作,本章節(jié)就重點跟蹤下SpringMVC的實際調(diào)用過程亿傅。
1媒峡、DispatcherServlet
記不記得,在大學(xué)期間或者剛接觸Java WEB開發(fā)的時候葵擎,前后端交互往往需要一個Servlet來接收請求谅阿,并返回信息。時至今日酬滤,Servlet仍不過時签餐。
如果是一個SpringMVC的項目,在WEB.XML里面需要配置一個DispatcherServlet盯串,它本質(zhì)就是個Servlet」谏悖或許我們還有印象,在請求到達(dá)的時候,就會調(diào)用到Servlet的service方法黄锤。那么负甸,說回到SpringMVC打月,就是FrameworkServlet類的service方法迫淹。
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);
}
}
經(jīng)過一系列的判斷匹配,最后調(diào)用到DispatcherServlet類的doService方法。可以看到的是,doService方法向request里面添加了很多默認(rèn)的屬性,如果需要,我們就可以拿到。并在最后調(diào)用了實際的處理方法习霹,doDispatch()伪阶。
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// 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);
doDispatch(request, response);
}
2筹误、doDispatch
實際處理的時候友存,大致分為幾個步驟膨俐。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
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);
//第一步 確定請求的處理器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
//URL沒有找到匹配項兄淫,返回404
noHandlerFound(processedRequest, response);
return;
}
// 第二步 確定請求的處理適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//第三步 調(diào)用攔截器 (方法調(diào)用前執(zhí)行)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 第四步 方法調(diào)用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//第五步 調(diào)用攔截器 (方法調(diào)用后執(zhí)行)
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
//第六步 處理方法返回 (返回頁面或者屬性)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//第七步 調(diào)用攔截器(請求完成后執(zhí)行)
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
不著急捕虽,下面我們一個一個的來看。
2.1坡脐、 獲取處理器 getHandler
獲取處理器泄私,就是根據(jù)請求的uri,匹配到Method方法。具體做法挖滤,我們在上一節(jié)Spring源碼分析(四)SpringMVC初始化做了預(yù)估崩溪,實際上Spring也確實是這樣做的。
根據(jù)uri獲取handlerMapping斩松,再以handlerMapping為key伶唯,從handlerMethods容器中拿到相應(yīng)的HandlerMethod對象。不過惧盹,它把HandlerMethod對象做了兩次封裝乳幸,源碼來看。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
//lookupPath就是uri钧椰,拿到mapping粹断。以/user/index為例,mapping如下:
//[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
List<T> directPathMatches = this.urlMap.get(lookupPath);
if (directPathMatches != null) {
//從handlerMethods容器中拿到Method對象,封裝成Match對象
//Match對象其實就兩個屬性 mapping和handlerMethod對象
addMatchingMappings(directPathMatches, matches, request);
}
HandlerMethod handler = matches.get(0).handlerMethod;
//最后返回的是HandlerExecutionChain對象
//里面包含兩個屬性嫡霞。handler>就是HandlerMethod對象
//還有個攔截器列表瓶埋。interceptorList,在第三步調(diào)用攔截器就是循環(huán)這個List來調(diào)用
return getHandlerExecutionChain(handler, request);
}
2.2诊沪、獲取適配器 getHandlerAdapter
這個比較簡單养筒。在初始化的時候,注冊了幾個適配器端姚。判斷上一步拿到的Handler是什么類型晕粪,就返回什么適配器,這里返回的是RequestMappingHandlerAdapter實例渐裸。
2.3、調(diào)用攔截器 (方法調(diào)用前執(zhí)行)
攔截器是鏈?zhǔn)秸{(diào)用尚氛,因為可能會有多個攔截器盆顾。攔截器的第一個方法怠褐,也是預(yù)處理方法preHandle是有返回值的。如果返回false您宪,整個請求就到此結(jié)束。在業(yè)務(wù)里磷杏,我們可以讓這個攔截器做一些校驗工作,不符合預(yù)期就返回false捏卓。需要注意的是interceptorIndex 這個變量极祸,它記錄當(dāng)前調(diào)用到了第幾個攔截器慈格。為什么要記錄這個呢浴捆?等看到后置攔截的時候我們就知道了稿械。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
if (getInterceptors() != null) {
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
//preHandle方法如果返回false,接著就調(diào)用攔截器的后置方法页眯。
//因為整個請求已經(jīng)結(jié)束了
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
2.4厢呵、方法調(diào)用
方法調(diào)用就是解析請求的參數(shù),拿到Method對象直接invoke即可碌奉。調(diào)用之后根據(jù)返回值渲染視圖寒砖。
2.4.1、參數(shù)解析
我們在上一章節(jié)已經(jīng)看到,SpringMVC初始化的時候茅逮,加載注冊了很多解析器的類判哥,其中有參數(shù)解析器和返回值類型解析器,如今就派上用場挺身。
一個HTTP請求的方法锌仅,不管有多少參數(shù),有多少種參數(shù)的類型热芹。最終伊脓,它們都是從哪來呢?沒錯株搔,就是Request。一切從Request而來纵隔。參數(shù)解析的方法最終返回的是一個Object[] args帆卓,就是參數(shù)值的數(shù)組。
- 拿到方法上的參數(shù)列表糊啡,循環(huán)此列表
- 調(diào)用解析器來解析參數(shù)吁津。它們的頂層接口是
HandlerMethodArgumentResolver
。它只有兩個方法梭依,supportsParameter典尾、resolveArgument钾埂。解析過程全靠這兩個方法,supports用來判斷是否應(yīng)該由此類解析姜性,resolve才是真正解析髓考。 - 返回參數(shù)值
private Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//獲取方法參數(shù)列表
MethodParameter[] parameters = getMethodParameters();
//args就是解析后的參數(shù)值的數(shù)組
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
if (args[i] != null) {
continue;
}
//判斷parameter是否能被某一個解析器所解析
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
//拿到上一步的解析器氨菇,解析拿到返回值放入args
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
}
}
return args;
}
下面我們看一下HttpServletRequest在解析器里面具體的實現(xiàn)。這個參數(shù)會調(diào)用到ServletRequestMethodArgumentResolver解析器射赛,里面其實是一些if else奶是。
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
if (WebRequest.class.isAssignableFrom(paramType)) {
return webRequest;
}
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (ServletRequest.class.isAssignableFrom(paramType) ) {
Object nativeRequest = webRequest.getNativeRequest(paramType);
return nativeRequest;
}
else if (HttpSession.class.isAssignableFrom(paramType)) {
return request.getSession();
}
else if (HttpMethod.class.equals(paramType)) {
return ((ServletWebRequest) webRequest).getHttpMethod();
}
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(paramType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(paramType)) {
return request.getReader();
}
//未完......
}
2.4.2、invoke
上一步通過各種解析器之后初嘹,返回一個Object類型的參數(shù)數(shù)組沮趣。有了Method對象,參數(shù)驻龟,調(diào)用就變得簡單了缸匪。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
2.4.3凌蔬、返回值的解析
invoke之后,方法返回Object 類型的returnValue懈词。上面說了辩诞,解析器分為參數(shù)解析器和返回值解析器兩種译暂。So,返回值解析器就是在這里被調(diào)用秧秉。
它的解析和參數(shù)解析器套路基本一致象迎,先判斷是否該由此類解析呛踊,然后交由該類解析。記得剛工作的時候汪厨,只要是返回頁面的愉择,都是通過new ModelAndView().setName("xxx")
來返回,后來發(fā)現(xiàn)直接返回視圖名字的字符串也可以衷戈,大感驚奇殖妇。原來,SpringMVC是在這里處理的疲吸。
我們來看這個類ViewNameMethodReturnValueHandler
的解析方法前鹅。
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果返回的值是字符串類型
//比如 /index,setViewName的工作它來搞
if (returnValue instanceof String) {
String viewName = (String) returnValue;
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
}
SpringMVC比較重要的一點是支持restful烦租,是通過ResponseBody注解除盏。它又是怎么處理的呢者蠕?在注冊HandlerAdapter的時候,默認(rèn)給它添加了消息轉(zhuǎn)換器粪小。里面有7種類型抡句,其中有一個MappingJacksonHttpMessageConverter就是專門負(fù)責(zé)ResponseBody注解的。
來到RequestResponseBodyMethodProcessor
類逞壁,它來負(fù)責(zé)匹配解析ResponseBody注解锐锣。判斷方法很簡單
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(),
ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
再來看它的handle方法雕憔。
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
//這個屬性很重要,在此設(shè)置為true分瘦,先記住,下面再看蟋恬。
mavContainer.setRequestHandled(true);
//具體用消息轉(zhuǎn)換器來寫入趁冈,這里的消息轉(zhuǎn)換器就是
//MappingJacksonHttpMessageConverter
writeWithMessageConverters(returnValue, returnType, webRequest);
}
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
//...........省略大部分代碼
//設(shè)置數(shù)據(jù)格式編碼等 application/json;charset=UTF-8
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
//messageConverters就是包含MappingJacksonHttpMessageConverter在內(nèi)的多種轉(zhuǎn)換器
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//先判斷是否可寫 判斷方式也很簡單渗勘,就是看mediaType是否是application/json
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
//拿到返回值
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) {
//寫的過程旺坠,先設(shè)置Response的頭信息、編碼蹋肮,再調(diào)用jackson.databind包里的方法寫入
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
//寫完之后刷新
//outputMessage.getBody().flush();
}
return;
}
}
}
}
2.4.4璧疗、獲取ModelAndView
方法調(diào)用完了崩侠,返回值也都解析了。視圖需不需要返回改抡,返回到哪里系瓢?ModelAndView來決定。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//在解析ResponseBody的時候阵赠,我們說有個屬性很重要肌稻。mavContainer.setRequestHandled(true);
//在這里就用到了爹谭,說明不需要視圖
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
return mav;
}
行文至此榛搔,關(guān)于方法調(diào)用算是都已經(jīng)處理完畢了。我們可以看出來腹泌,其實調(diào)用很簡單凉袱, 關(guān)鍵在于做好參數(shù)解析和返回值解析的工作。
2.5钟鸵、調(diào)用攔截器 (方法調(diào)用后執(zhí)行)
又到了一個攔截器的調(diào)用涤躲。這次它調(diào)用的是postHandle方法。
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request, response, this.handler, mv);
}
2.6蒙袍、處理結(jié)果
這里是真正響應(yīng)請求的地方嫩挤。先是判斷ModelAndView是否為空俐镐,如果為空說明返回的不是視圖,就沒必要往下執(zhí)行叼风。
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
重點在于render方法棍苹。它最終調(diào)用了renderMergedOutputModel方法枢里,渲染輸出。我們在配置文件中彬碱,都要配置一個視圖解析器viewResolver奥洼,這里就是用到的它。解析出來完整的視圖路徑后嚼沿,利用Servlet的RequestDispatcher直接做轉(zhuǎn)發(fā)就完成了這一步的工作。
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
//獲取返回的路徑 視圖解析器配置的prefix加上返回的viewName遣妥,再加上后綴p:suffix
String dispatcherPath = prepareForRendering(requestToExpose, response);
//獲取RequestDispatcher箫踩,
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
//直接轉(zhuǎn)發(fā)
rd.forward(requestToExpose, response);
}
2.7辨图、調(diào)用攔截器 (視圖渲染后執(zhí)行)
在調(diào)用攔截器的預(yù)處理方法時,提到了一個變量:interceptorIndex 吱韭。在這里就能看到它的作用鱼的。攔截器可能是多個的凑阶,它是鏈?zhǔn)秸{(diào)用的過程。比如有5個攔截器姨俩。如果在第3個攔截器的preHandler方法返回了false师郑,后兩個攔截器的After不應(yīng)該再被執(zhí)行宝冕。所以在后置攔截方法是從interceptorIndex 開始的。
void triggerAfterCompletion(HttpServletRequest request,
HttpServletResponse response, Exception ex)throws Exception {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
}
}
3菊卷、總結(jié)
以上就是SpringMVC處理一個請求的所有流程宝剖。DispatcherServlet其本質(zhì)就是個Servlet万细,一切請求經(jīng)過它來處理。它根據(jù)請求的uri找到對應(yīng)的Method對象襟雷,然后從Request中拿到參數(shù)解析成我們想要的類型仁烹,調(diào)用具體方法。通過不同的返回值解析器來確定返回的數(shù)據(jù)的類型是什么计呈,需不需要響應(yīng)視圖捌显。然后調(diào)用RequestDispatcher 直接轉(zhuǎn)發(fā)总寒。最后通過HandlerInterceptor可以讓我們有機會參與到SpringMVC處理環(huán)節(jié)中去,在具體方法執(zhí)行的不同時機加入我們自定義的業(yè)務(wù)善镰。