javascript
SpringBoot各类型参数解析原理(源码)
上次那篇我們只分析了doDispatch中的getHandler方法(獲取執(zhí)行鏈,執(zhí)行鏈里包括當(dāng)前請(qǐng)求URL對(duì)應(yīng)的 handler 以及攔截器(Controller、method綁定關(guān)系)),今兒繼續(xù)向下看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;}//繼續(xù)向下// 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;}//開始真正處理請(qǐng)求的方法mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} }一、getHandlerAdapter
有沒有想過,為何我們加了那些注解,例如@PathVariable,為什么springmvc就能將其變量確定為對(duì)應(yīng)的值呢?這就是HandlerAdapter的作用,在getHandler方法確定好控制器和對(duì)應(yīng)的方法后(執(zhí)行鏈),getHandlerAdapter就會(huì)來幫我們?yōu)楫?dāng)前的handler找一個(gè)adapter然后我們通過該適配器,就能夠?qū)⒄?qǐng)求的鏈接所帶的參數(shù)給適配上。
看一下DispatcherServlet的doService方法時(shí)序圖:
直接進(jìn)入getHandlerAdapter方法查看,debug -getHandlerAdapter方法,可以看到,會(huì)在原生的4種handlerAdapter中選擇一個(gè)匹配的適配器進(jìn)行返回。獲取代碼:
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");} }
對(duì)應(yīng)的處理如下:
注意:如果自己添加了Adapter就不會(huì)在加載springMVC默認(rèn)的這些Adapter
getHandlerAdapter里調(diào)用了adapter.supports(handler)
- 通過supports方法來確定adapter,我們進(jìn)入supports方法,發(fā)現(xiàn)不同的adapter有不同的判斷方法,我們還是先以requestMapping請(qǐng)求的到的handler為例
- 可以發(fā)現(xiàn)他的判斷方式很簡(jiǎn)單,就是判斷handler是不是一個(gè)HandlerMethod(在上面匹配的時(shí)候會(huì)根據(jù)不同的情況獲得不同的handler)
我們可以通過debug其他類型的handler可以返現(xiàn)他們的判斷方式和上面的類似都是instanceof來判斷的,匹配后返回具體handleradapter通過getHandler()和getHandlerAdapter()方法得到的執(zhí)行鏈(得到controller中具體的執(zhí)行方法)和適配器(可以解析請(qǐng)求所帶的參數(shù))后,我們就可以來真正執(zhí)行請(qǐng)求的方法(handle())了。
二、handle
執(zhí)行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());來到
進(jìn)一步,進(jìn)入到該抽象類的實(shí)現(xiàn)類RequestMappingHandlerAdapter中(為何是RequestMappingHandlerAdapter),因?yàn)槲业姆椒ㄊ褂昧?#64;RequestMapping,所以就返回這個(gè)Adapter),對(duì)一個(gè)請(qǐng)求方法的所有操作都會(huì)在這里進(jìn)行。RequestMappingHandlerAdapter 部分源碼如下:可以看到,handleInternal執(zhí)行后,會(huì)返回一個(gè)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;}其中有兩個(gè)變量值得我們研究:argumentResolvers(參數(shù)解析器 26種)和returnValueHandlers(返回值處理器 15種),這兩個(gè)東西就是這篇文章的主題:參數(shù)解析的核心。
看一下HandlerMethodArgumentResolver接口的定義:
該接口作用:當(dāng)前解析器是否支持解析這種參數(shù),支持就調(diào)用 resolveArgument解析最終確定將要執(zhí)行的目標(biāo)方法的每一個(gè)參數(shù)的值是什么SpringMVC目標(biāo)方法能寫多少種參數(shù)類型。取決于參數(shù)解析器,默認(rèn)26種:
決定了目標(biāo)方法到底能寫多少種類型的返回值,默認(rèn)15種
有一個(gè)值得注意的處理器就是RequestResponseBodyMethodHandler,就是我們使用@ResponseBody時(shí),使用的處理器,底層如下:
在將參數(shù)解析器和返回值處理器設(shè)置好后,進(jìn)一步調(diào)用了invokeAndHandle方法,跟蹤該方法,我們來到:ServletInvocableHandlerMethod類中的```invokeAndHandle方法
部分源碼:
跟蹤invokeForRequest,來到InvocableHandlerMethod類, invokeForRequest及getMethodArgumentValues(開始解析參數(shù)了)源碼
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);}//核心方法,獲取參數(shù)值最底層的方法protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {//獲取到方法的所有參數(shù)聲明(例如注解,索引,類型)MethodParameter[] parameters = this.getMethodParameters();//判斷參數(shù)是否為空,為空直接返回,無須確定任何值if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;} else {Object[] args = new Object[parameters.length];//挨個(gè)遍歷參數(shù)取值for(int i = 0; i < parameters.length; ++i) {MethodParameter parameter = parameters[i];//確定參數(shù)名字parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] == null) {//先判斷當(dāng)前解析器是否支持這種類型,不支持便對(duì)解析器遍歷,直到找到支持的解析器//具體調(diào)用鏈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;}} }獲取到的參數(shù)聲明:
HandlerMethodArgumentResolverComposite.getArgumentResolver源碼:
這里可以看到對(duì)我們上面提到的那26種解析器的遍歷,最后會(huì)完全緩存在springboot的本地緩存中
拿到參數(shù)解析器后,我們就可以來獲取參數(shù)的值了
HandlerMethodArgumentResolverComposite.ArgumentResolver源碼:
resolveArgument最終會(huì)調(diào)用AbstractNamedValueMethodArgumentResolver的各種實(shí)現(xiàn)類如下:
再配合UrlPathHelper(會(huì)將url中的變量解析出來,放在request的請(qǐng)求域中),最終得到變量值。
三、對(duì)于傳入的是Servlet API的參數(shù)的處理
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId這些都能找到對(duì)應(yīng)的resolver進(jìn)行解析。
以ServletRequestMethodArgumentResolver為例,它能解析以下的參數(shù),總之,就是進(jìn)行到resolvers.supportsParameter(parameter)這個(gè)方法后,遍歷那26個(gè)參數(shù)解析器,拿到對(duì)應(yīng)的解析器去解析就好了,原理都是一樣的
四、復(fù)雜參數(shù)的處理
復(fù)雜參數(shù)如:Map、Model(map、model里面的數(shù)據(jù)會(huì)被放在request的請(qǐng)求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向攜帶數(shù)據(jù))、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder等
那么重點(diǎn)就是,他是怎么給request域放數(shù)據(jù)的呢:debug以下方法看一下
而getModel()這個(gè)方法他會(huì)返回一個(gè)ModeMap類型的數(shù)據(jù),源碼如下:
最終,他是返回一個(gè)ModelMap的子類BindingAwareModelMap,BindingAwareModelMap 是Model 也是Map
繼承樹如下:
Model參數(shù)類型就調(diào)用另一個(gè)解析器
debug后發(fā)現(xiàn),居然跟解析Map類型調(diào)用的是一樣的方法,也是來到了mavContainer.getModel()這個(gè)方法,準(zhǔn)備獲取模型數(shù)據(jù)。我們可以發(fā)現(xiàn),兩者返回的是同一個(gè)BindingAwareModelMap。同時(shí),直接放心讓request,和response對(duì)象也解析好。
然后我們放行方法,執(zhí)行完invokeForRequest方法,此時(shí),我們知道,對(duì)于請(qǐng)求的處理已經(jīng)完成了,接下來就是視圖解析了,這里先不討論視圖解析的流程,就研究forward的時(shí)候,spring是如何將數(shù)據(jù)(model)放在請(qǐng)求域中給轉(zhuǎn)發(fā)出去的。
跟蹤進(jìn)去,我們發(fā)現(xiàn)在處理返回結(jié)果的時(shí)候,也把mavContainer傳進(jìn)去了:
mavContainer此時(shí)如下:
handleReturnValue方法:
如果你放回的類型是個(gè)字符串,就把字符串設(shè)置成viewName
此時(shí)的mavContainer(view已經(jīng)為“”forward:/success)
至此可以得出一個(gè)結(jié)論:方法執(zhí)行完成后,springmvc會(huì)所有的數(shù)據(jù)都放在 ModelAndViewContainer;包含要去的頁面地址View。還包含Model數(shù)據(jù)。然后進(jìn)一步對(duì)這些數(shù)據(jù)進(jìn)行處理(渲染),會(huì)執(zhí)行以下:
繼續(xù)跟蹤
仍然可以看到,還是圍繞著處理mavContainer展開,ModelFactory里有一個(gè)
updateBindingResult方法,這是關(guān)鍵,它會(huì)遍歷所有model的值,并根據(jù)綁定策略對(duì)數(shù)據(jù)進(jìn)行封裝
然后在執(zhí)行:ModelAndView mav=new ModelAndView(....);這一句,即把遍歷到的model數(shù)據(jù)生成一個(gè)ModelAndView。然后再根據(jù)是不是重定向,轉(zhuǎn)發(fā),或者普通處理,再進(jìn)一步對(duì)數(shù)據(jù)進(jìn)行處理
此時(shí),DispatchServlet的:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());執(zhí)行完成,開始執(zhí)行DispatchServlet的另一個(gè)方法:
//完成業(yè)務(wù)處理后的后置處理mappedHandler.applyPostHandle(processedRequest, response, mv);開始執(zhí)行render()方法。
涉及兩個(gè)主要方法:
//處理派發(fā)結(jié)果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); //渲染合并輸出模型(最關(guān)鍵的核心) 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);}} 暴露模型作為請(qǐng)求域?qū)傩?// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request); //該方法可以看出,底層最終就是通過最普通的遍歷,將model數(shù)據(jù)重新放入請(qǐng)求域中 protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有數(shù)據(jù)遍歷挨個(gè)放在請(qǐng)求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}五、自定義POJO類型參數(shù)的處理
跟上面一樣,來到resolvers.supportsParameter(parameter),處理POJO類型的有兩個(gè)參數(shù)解析器,都是叫:ServletModelAttributeMethodProcessor,但是一個(gè)是處理帶注解的bean,一個(gè)是處理不帶注解的bean。
判斷時(shí),先判斷參數(shù)是不是簡(jiǎn)單類型
而自定義對(duì)象,自然就不是簡(jiǎn)單類型
然后便開始執(zhí)行resolveArgument方法。
- WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);核心方法,將請(qǐng)求參數(shù)的值綁定到指定的JavaBean里面,WebDataBinder 利用它里面的 Converters將請(qǐng)求數(shù)據(jù)轉(zhuǎn)成指定的數(shù)據(jù)類型。再次封裝到JavaBean中
- Converters :底層默認(rèn)有124個(gè),如下:
我們也可以自定義自己的Converters:
總結(jié)
以上是生活随笔為你收集整理的SpringBoot各类型参数解析原理(源码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互联网晚报 | 1月11日 星期二 |
- 下一篇: 互联网日报 | 6月9日 星期三 | 腾