前言
SpringMVC請求處理相信大家都很熟悉了,本篇主要是基于SpringMVC處理請求的流程來閱讀并除錯原始碼,以及解決幾個僅靠流程圖無法解釋的問題,
關于Spring MVC的流程思維導圖分享給大家:
Spring系列的學習筆記和面試題,包含spring面試題、spring cloud面試題、spring boot面試題、spring教程筆記、spring boot教程筆記、最新阿里巴巴開發手冊(63頁PDF總結)、2020年Java面試手冊,一共整理了1184頁PDF檔案,
關注公眾號:程式員白楠楠,即可獲取這份1184頁PDF檔案的spring全家桶資料,
本篇使用的Spring版本為5.2.2.RELEASE
九大組件
SpringMVC幾乎所有的功能都由九大組件來完成,所以明白九大組件的作用,對于學習SpringMVC來說非常重要,
/** 檔案上傳決議器 */
private MultipartResolver multipartResolver;
/** 區域決議器,用于國際化 */
private LocaleResolver localeResolver;
/** 主題決議器 */
private ThemeResolver themeResolver;
/** Handler映射資訊 */
private List<HandlerMapping> handlerMappings;
/** Handler配接器*/
private List<HandlerAdapter> handlerAdapters;
/** Handler執行例外決議器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** 請求到視圖的轉換器 */
private RequestToViewNameTranslator viewNameTranslator;
/** SpringMVC允許重定向時攜帶引數,存在session中,用完就銷毀,所以叫FlashMap */
private FlashMapManager flashMapManager;
/** 視圖決議器 */
private List<ViewResolver> viewResolvers;
-
HandlerMapping:Handler映射資訊,根據請求攜帶的url資訊查找處理器(Handler),每個請求都需要對應的Handler處理,
-
HandlerAdapter:Handler配接器,SpringMVC沒有直接呼叫處理器(Handler),而是通過HandlerAdapter來呼叫,主要是為了統一Handler的呼叫方式
-
ViewResolver:視圖決議器,用來將字串型別的視圖名稱決議為View型別的視圖,ViewResolver需要找到渲染所用的模板和所用的技術(也就是視圖的型別)進行渲染,具體的渲染程序則交由不同的視圖自己完成,
-
MultipartResolver:檔案上傳決議器,主要用來處理檔案上傳請求
-
HandlerExceptionResolver:Handler執行例外決議器,用來對例外進行統一處理
-
RequestToViewNameTranslator:請求到視圖的轉換器
-
LocaleResolver:區域決議器,用于支持國際化
-
FlashMapManager:SpringMVC允許重定向時攜帶引數,存在session中,用完就銷毀,所以叫FlashMap
-
ThemeResolver:主題決議器,用于支持不同的主題
九大組件中最重的的前三個,HandlerMapping、HandlerAdapter和ViewResolver,因為這是閱讀原始碼時,避不開的三個組件,
我把 Spring MVC 相關的技術文章整理成了 PDF,老規矩,關注微信公眾號 Java后端 回復 666 下載,
除錯準備
搭建一個基本的Spring web專案即可
Controller部分
@Controller
public class IndexController {
@RequestMapping("/index/home")
public String home(String id, Student student, @RequestParam("code") String code) {
System.out.println(student.getName());
return "index";
}
@ResponseBody
@RequestMapping("/index/list")
public String list() {
return "success";
}
}
Entity部分
public class Student {
private String name;
private Integer gender;
// getter、setter
}
還是那句話,Spring原始碼非常龐大,不能只見樹木不見森林,需要有針對性的閱讀,所以本篇只需要關注主體流程即可,
核心方法
我們都知道,SpringMVC有一個用來分發請求的前端控制器DispatcherServlet,其中用來處理請求的方法就是doService,該方法定義如下
doService
/**
* 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 {
logRequest(request);
// 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)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
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());
if (this.flashMapManager != null) {
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()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doDispatch
doDispatch是doService中真正用來處理請求的方法
/**
* 實際處理請求的方法
*/
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.
// 為當前請求找到一個合適的處理器(Handler)
// 回傳值是一個HandlerExecutionChain,也就是處理器執行鏈
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根據HandlerExecutionChain攜帶的Handler找到合適的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 處理GET請求的快取
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 執行攔截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 利用HandlerAdapter來執行Handler里對應的處理方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果沒有設定視圖,則應用默認的視圖名
applyDefaultViewName(processedRequest, mv);
// 執行攔截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 根據ModelAndView物件決議視圖
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
該方法就是SpringMVC處理請求的整體流程,其中涉及到幾個重要的方法,
getHandler
該方法定義如下
/**
* Return the HandlerExecutionChain for this request.
* 為這個request回傳一個HandlerExecutionChain
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
除錯資訊如下
根據除錯資訊可以看出,getHandler方法主要是從List handlerMappings集合中遍歷查找一個合適的處理器(Handler),回傳的結果是一個HandlerExecutionChain,然后再根據HandlerExecutionChain里攜帶的Handler去獲取HandlerAdapter,
getHandlerAdapter
getHandlerAdapter方法定義如下
/**
* 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 {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
除錯資訊如下
同樣getHandlerAdapter方法主要是從List handlerAdapters集合中遍歷查找一個合適的處理器配接器(HandlerAdapter),回傳的結果是一個HandlerAdapter,
可以看到此處HandlerAdapter真正的實作類是RequestMappingHandlerAdapter,
processDispatchResult
processDispatchResult方法主要根據方法執行完成后封裝的ModelAndView,轉發到對應頁面,定義如下
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 主要呼叫該方法渲染視圖
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
render
render方法定義如下
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 根據給定的視圖名稱,決議獲取View物件
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
resolveViewName
resolveViewName方法定義如下
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
除錯資訊如下
根據除錯資訊可以看到真正決議視圖的ViewResolver的是InternalResourceViewResolver類,也就是我們經常配置的一項型別
<!-- 定義視圖檔案決議 -->
<bean >
<property name="prefix" value="https://www.cnblogs.com/WEB-INF/views/" />
<property name="suffix" value="https://www.cnblogs.com/bainannan/p/.html" />
</bean>
至此我們就得到了SpringMVC處理請求的完整邏輯
SpringMVC處理請求的整個流程已經梳理清楚了,
但是,有兩個重要的問題沒有解決,那就是:引數系結和回傳值處理,
> 因為在撰寫Controller里面的方法的時候,各種型別的引數都有,SpringMVC是怎么處理不同型別的引數的呢? > SpringMVC處理請求完成后,一定會回傳ModelAndView嗎,如果加了@ResponseBody注解呢?
引數系結
在整個流程中,還有一個最重要的方法,那就是真正執行handler的方法,引數的系結和回傳值的處理都在這個方法里,也就是
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
handle
handle方法的作用是根據請求引數,執行真正的處理方法,并且回傳合適的ModelAndView物件,也有可能回傳null,該方法定義如下 在AbstractHandlerMethodAdapter類中
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
可以看到這個方法實作只有一行代碼
handleInternal
繼續深入handleInternal方法
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 校驗指定的請求以獲取受支持的方法型別(GET、POST等)和所需的session
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
// 真正執行handler的方法
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
invokeHandlerMethod
繼續深入invokeHandlerMethod方法
/**
* Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
* if view resolution is required.
* 執行@RequestMapping標注的handler方法,如果需要決議視圖就準備一個ModelAndView
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// HandlerMethod介面封裝執行方法的資訊,提供對方法引數,方法回傳值,方法注釋等的便捷訪問,
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// ModelAndViewContainer可以看做ModelAndView的背景關系容器,關聯著Model和View的資訊
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 真正執行Handler的方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 獲取ModelAndeView物件
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
invokeAndHandle
invokeAndHandle方法的作用是執行并處理真正回應請求的方法,該方法定義如下
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 執行handler的方法
Object returnValue = https://www.cnblogs.com/bainannan/p/invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null,"No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
invokeForRequest
/**
* Invoke the method after resolving its argument values in the context of the given request.
* <p>Argument values are commonly resolved through
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
* The {@code providedArgs} parameter however may supply argument values to be used directly,
* i.e. without argument resolution. Examples of provided argument values include a
* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
* Provided argument values are checked before argument resolvers.
* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
* resolved arguments.
* @param request the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
* @return the raw value returned by the invoked method
* @throws Exception raised if no suitable argument resolver can be found,
* or if the method raised an exception
* @see #getMethodArgumentValues
* @see #doInvoke
*/
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 獲取引數
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 執行
return doInvoke(args);
}
真正的執行無非就是通過反射invoke,所以更重要的是引數是如何系結的,詳情就在getMethodArgumentValues方法
getMethodArgumentValues
getMethodArgumentValues方法用于從request請求中獲取真正的引數,回傳的是Object陣列,該方法定義如下
/**
* Get the method argument values for the current request, checking the provided
* argument values and falling back to the configured argument resolvers.
* <p>The resulting array will be passed into {@link #doInvoke}.
* @since 5.1.2
*/
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 獲取方法上所有的引數
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
根據除錯資訊可以看到,用來處理請求引數的類是HandlerMethodArgumentResolver介面的實作類HandlerMethodArgumentResolverComposite,此時正在處理的引數是一個Student物件,并且已經把值注系結了,也就是說真正執行系結的是方法resolveArgument
resolveArgument
resolveArgument是真正執行系結的的方法
/**
* Iterate over registered
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
* and invoke the one that supports it.
* @throws IllegalArgumentException if no suitable argument resolver is found
*/
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 獲取合適的引數決議器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 執行引數系結
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
getArgumentResolver
getArgumentResolver該方法用于執行引數的系結,定義如下
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports
* the given method parameter.
*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
該方法的邏輯就是先從argumentResolver快取中找到能夠執行引數系結的HandlerMethodArgumentResolver,如果找不到就從HandlerMethodArgumentResolver找,SpringMVC支持的HandlerMethodArgumentResolver一共有26種,用來決議各種型別的引數
根據博主的除錯可以知道
-
RequestParamMethodArgumentResolver:處理普通引數(基本型別、包裝型別、String),不管加不加@RequestParam注解
-
ServletModelAttributeMethodProcessor:處理POJO型別的引數,比如自定義的Student物件
-
RequestResponseBodyMethodProcessor:處理@RequestBody注解型別的引數
resolveArgument
由于不同型別的引數有不同的HandlerMethodArgumentResolver來處理,此處選取POJO型別引數的注入實作,對應的引數決議類是ModelAttributeMethodProcessor,其中resolveArgument方法用來決議(系結)引數方法定義如下
/**
* Resolve the argument from the model or if not found instantiate it with
* its default if it is available. The model attribute is then populated
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}
* @throws Exception if WebDataBinder initialization fails
*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
// 獲取引數名
String name = ModelFactory.getNameForParameter(parameter);
// 獲取引數上的ModelAttribute注解
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
// 創建引數型別的實體(未注入值),底層就是通過反射呼叫構造方法
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 真正執行系結(值注入)的方法
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
根據除錯資訊也可以看到bindRequestParameters(binder, webRequest)執行完成之后,POJO型別的引數已經完成了系結,
bindRequestParameters
/**
* This implementation downcasts {@link WebDataBinder} to
* {@link ServletRequestDataBinder} before binding.
* @see ServletRequestDataBinderFactory
*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
// 執行系結的方法
servletBinder.bind(servletRequest);
}
bind
繼續深入bind方法
public void bind(ServletRequest request) {
// 獲取所有引數的鍵值對
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
// 處理檔案上傳請求
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
// 把url中攜帶的引數也加入到MutablePropertyValues
addBindValues(mpvs, request);
// 執行系結(注入值)
doBind(mpvs);
}
由于呼叫層次過深,所以無法一步步列出下面的步驟,doBind方法的原理還是通過呼叫POJO物件里的setter方法設定值,可以查看最終的除錯資訊
根據除錯資訊可以看到,最終執行的還是POJO物件的setter方法,具體執行的類是BeanWrapperImpl,
了解了引數的系結,再來看回傳值的處理,
回傳值處理
invokeAndHandle
回到原始碼invokeAndHandle方法處(ServletInvocableHandlerMethod類中),該方法定義如下
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = https://www.cnblogs.com/bainannan/p/invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null,"No return value handlers");
try {
// 真正處理不同型別回傳值的方法
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
真正處理不同型別的回傳值的方法是handleReturnValue方法
handleReturnValue
/**
* Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
* @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
*/
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 根據回傳值個回傳值型別選取合適的HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 真正的處理回傳值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
selectHandler
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = https://www.cnblogs.com/bainannan/p/isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
根據除錯資訊可以看到,SpringMVC為回傳值提供了15個HandlerMethodReturnValueHandler的實作了來處理不同型別的回傳值,
事實上,用來處理@ResponseBody型別的是RequestResponseBodyMethodProcessor,
如果對前文引數系結還有印象的話,會發現@RequestBody型別引數系結也是用的這個類,
繼續跟進RequestResponseBodyMethodProcessor類的handleReturnValue方法
handleReturnValue
RequestResponseBodyMethodProcessor類的handleReturnValue方法定義如下
這里設定了一個非常重要的屬性requestHandled,這個屬性關系到是否需要回傳ModelAndView物件
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 設定該請求是否已在處理程式中完全處理,例如@ResponseBody方法不需要視圖決議器,此處就可以設定為true,
// 當控制器方法宣告型別為ServletResponse或OutputStream的引數時,也可以設定此標志為true,
// 這個屬性設定成true之后,上層getModelAndView獲取ModelAndView時會回傳Null,因為不需要視圖,
// 默認值為false
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 底層就是利用java.io.OutputStreamWriter類把回傳值寫到網路IO
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
繼續深入writeWithMessageConverters方法,一步步除錯到最后,底層就是利用java.io.OutputStreamWriter類把回傳值寫到網路IO
由于handleReturnValue把requestHandled設定成了true,上層在呼叫getModelAndView方法時會回傳null,表示該請求不需要視圖,感興趣的同學自己除錯一下便知,
總結
本文主要從原始碼的閱讀和除錯的角度,整體的講解了SpringMVC處理請求的整個流程,并且講解了引數的系結以及回傳值的處理,相信大家看完后,結合自己的除錯資訊,會對SpringMVC的請求處理程序有一個更深入的理解,
Spring系列的學習筆記和面試題,包含spring面試題、spring cloud面試題、spring boot面試題、spring教程筆記、spring boot教程筆記、最新阿里巴巴開發手冊(63頁PDF總結)、2020年Java面試手冊,一共整理了1184頁PDF檔案,
關注公眾號:程式員白楠楠,即可獲取這份1184頁PDF檔案的spring全家桶資料,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231291.html
標籤:Java
