SpringBoot各类型参数解析原理(源码)
上次那篇我們只分析了doDispatch中的getHandler方法(獲取執行鏈,執行鏈里包括當前請求URL對應的 handler 以及攔截器(Controller、method綁定關系)),今兒繼續向下看getHandlerAdapter方法和handle方法
public class DispatcherServlet{protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}//繼續向下// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());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;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//開始真正處理請求的方法mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} }一、getHandlerAdapter
有沒有想過,為何我們加了那些注解,例如@PathVariable,為什么springmvc就能將其變量確定為對應的值呢?這就是HandlerAdapter的作用,在getHandler方法確定好控制器和對應的方法后(執行鏈),getHandlerAdapter就會來幫我們為當前的handler找一個adapter然后我們通過該適配器,就能夠將請求的鏈接所帶的參數給適配上。
看一下DispatcherServlet的doService方法時序圖:
直接進入getHandlerAdapter方法查看,debug -getHandlerAdapter方法,可以看到,會在原生的4種handlerAdapter中選擇一個匹配的適配器進行返回。獲取代碼:
public class DispatcherServlet{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");} }
對應的處理如下:
注意:如果自己添加了Adapter就不會在加載springMVC默認的這些Adapter
getHandlerAdapter里調用了adapter.supports(handler)
- 通過supports方法來確定adapter,我們進入supports方法,發現不同的adapter有不同的判斷方法,我們還是先以requestMapping請求的到的handler為例
- 可以發現他的判斷方式很簡單,就是判斷handler是不是一個HandlerMethod(在上面匹配的時候會根據不同的情況獲得不同的handler)
我們可以通過debug其他類型的handler可以返現他們的判斷方式和上面的類似都是instanceof來判斷的,匹配后返回具體handleradapter通過getHandler()和getHandlerAdapter()方法得到的執行鏈(得到controller中具體的執行方法)和適配器(可以解析請求所帶的參數)后,我們就可以來真正執行請求的方法(handle())了。
二、handle
執行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());來到
進一步,進入到該抽象類的實現類RequestMappingHandlerAdapter中(為何是RequestMappingHandlerAdapter),因為我的方法使用了@RequestMapping,所以就返回這個Adapter),對一個請求方法的所有操作都會在這里進行。RequestMappingHandlerAdapter 部分源碼如下:可以看到,handleInternal執行后,會返回一個ModelAndView
invokeHandlerMethod方法源碼:
@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);Object result;try {WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = this.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 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()) {result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(this.logger, (traceOn) -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);if (!asyncManager.isConcurrentHandlingStarted()) {ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);return var15;}result = null;} finally {webRequest.requestCompleted();}return (ModelAndView)result;}其中有兩個變量值得我們研究:argumentResolvers(參數解析器 26種)和returnValueHandlers(返回值處理器 15種),這兩個東西就是這篇文章的主題:參數解析的核心。
看一下HandlerMethodArgumentResolver接口的定義:
該接口作用:當前解析器是否支持解析這種參數,支持就調用 resolveArgument解析最終確定將要執行的目標方法的每一個參數的值是什么SpringMVC目標方法能寫多少種參數類型。取決于參數解析器,默認26種:
決定了目標方法到底能寫多少種類型的返回值,默認15種
有一個值得注意的處理器就是RequestResponseBodyMethodHandler,就是我們使用@ResponseBody時,使用的處理器,底層如下:
在將參數解析器和返回值處理器設置好后,進一步調用了invokeAndHandle方法,跟蹤該方法,我們來到:ServletInvocableHandlerMethod類中的```invokeAndHandle方法
部分源碼:
跟蹤invokeForRequest,來到InvocableHandlerMethod類, invokeForRequest及getMethodArgumentValues(開始解析參數了)源碼
public class InvocableHandlerMethod extends HandlerMethod {@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}return this.doInvoke(args);}//核心方法,獲取參數值最底層的方法protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {//獲取到方法的所有參數聲明(例如注解,索引,類型)MethodParameter[] parameters = this.getMethodParameters();//判斷參數是否為空,為空直接返回,無須確定任何值if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;} else {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) {//先判斷當前解析器是否支持這種類型,不支持便對解析器遍歷,直到找到支持的解析器//具體調用鏈supportsParameter->HandlerMethodArgumentResolverComposite.getArgumentResolver-> 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 var10) {if (logger.isDebugEnabled()) {String exMsg = var10.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw var10;}}}return args;}} }獲取到的參數聲明:
HandlerMethodArgumentResolverComposite.getArgumentResolver源碼:
這里可以看到對我們上面提到的那26種解析器的遍歷,最后會完全緩存在springboot的本地緩存中
拿到參數解析器后,我們就可以來獲取參數的值了
HandlerMethodArgumentResolverComposite.ArgumentResolver源碼:
resolveArgument最終會調用AbstractNamedValueMethodArgumentResolver的各種實現類如下:
再配合UrlPathHelper(會將url中的變量解析出來,放在request的請求域中),最終得到變量值。
三、對于傳入的是Servlet API的參數的處理
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId這些都能找到對應的resolver進行解析。
以ServletRequestMethodArgumentResolver為例,它能解析以下的參數,總之,就是進行到resolvers.supportsParameter(parameter)這個方法后,遍歷那26個參數解析器,拿到對應的解析器去解析就好了,原理都是一樣的
四、復雜參數的處理
復雜參數如:Map、Model(map、model里面的數據會被放在request的請求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向攜帶數據)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder等
那么重點就是,他是怎么給request域放數據的呢:debug以下方法看一下
而getModel()這個方法他會返回一個ModeMap類型的數據,源碼如下:
最終,他是返回一個ModelMap的子類BindingAwareModelMap,BindingAwareModelMap 是Model 也是Map
繼承樹如下:
Model參數類型就調用另一個解析器
debug后發現,居然跟解析Map類型調用的是一樣的方法,也是來到了mavContainer.getModel()這個方法,準備獲取模型數據。我們可以發現,兩者返回的是同一個BindingAwareModelMap。同時,直接放心讓request,和response對象也解析好。
然后我們放行方法,執行完invokeForRequest方法,此時,我們知道,對于請求的處理已經完成了,接下來就是視圖解析了,這里先不討論視圖解析的流程,就研究forward的時候,spring是如何將數據(model)放在請求域中給轉發出去的。
跟蹤進去,我們發現在處理返回結果的時候,也把mavContainer傳進去了:
mavContainer此時如下:
handleReturnValue方法:
如果你放回的類型是個字符串,就把字符串設置成viewName
此時的mavContainer(view已經為“”forward:/success)
至此可以得出一個結論:方法執行完成后,springmvc會所有的數據都放在 ModelAndViewContainer;包含要去的頁面地址View。還包含Model數據。然后進一步對這些數據進行處理(渲染),會執行以下:
繼續跟蹤
仍然可以看到,還是圍繞著處理mavContainer展開,ModelFactory里有一個
updateBindingResult方法,這是關鍵,它會遍歷所有model的值,并根據綁定策略對數據進行封裝
然后在執行:ModelAndView mav=new ModelAndView(....);這一句,即把遍歷到的model數據生成一個ModelAndView。然后再根據是不是重定向,轉發,或者普通處理,再進一步對數據進行處理
此時,DispatchServlet的:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());執行完成,開始執行DispatchServlet的另一個方法:
//完成業務處理后的后置處理mappedHandler.applyPostHandle(processedRequest, response, mv);開始執行render()方法。
涉及兩個主要方法:
//處理派發結果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); //渲染合并輸出模型(最關鍵的核心) renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); @Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}} 暴露模型作為請求域屬性 // Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request); //該方法可以看出,底層最終就是通過最普通的遍歷,將model數據重新放入請求域中 protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有數據遍歷挨個放在請求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}五、自定義POJO類型參數的處理
跟上面一樣,來到resolvers.supportsParameter(parameter),處理POJO類型的有兩個參數解析器,都是叫:ServletModelAttributeMethodProcessor,但是一個是處理帶注解的bean,一個是處理不帶注解的bean。
判斷時,先判斷參數是不是簡單類型
而自定義對象,自然就不是簡單類型
然后便開始執行resolveArgument方法。
- WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);核心方法,將請求參數的值綁定到指定的JavaBean里面,WebDataBinder 利用它里面的 Converters將請求數據轉成指定的數據類型。再次封裝到JavaBean中
- Converters :底層默認有124個,如下:
我們也可以自定義自己的Converters:
總結
以上是生活随笔為你收集整理的SpringBoot各类型参数解析原理(源码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网晚报 | 1月11日 星期二 |
- 下一篇: SpringBoot自定义转换器(Con