springboot-springmvc请求参数获取与原理【长文预警,收藏慢啃】
目錄
一、參數(shù)注解
1.@PathVariable
2.@RequestHeader
3.@RequestParam
4.@CookieValue
5.@RequestBody
6.@RequestAttribute
7.@MatrixVariable
二、參數(shù)解析原理
1.DispatcherServlet類的doDispatch方法
2.HandlerAdapter
3.執(zhí)行目標(biāo)方法
4.參數(shù)解析器-HandlerMethodArgumentResolver
5.返回值處理器
三、使用Servlet API作為參數(shù)
1.使用
四、使用Servlet API作為參數(shù)原理
1.InvocableHandlerMethod的getMethodArgumentValues方法
2.HttpServletRequest作為參數(shù)的參數(shù)處理器
五、復(fù)雜參數(shù)的解析
1.使用
六、復(fù)雜參數(shù)解析原理
1.還是同樣會(huì)進(jìn)入到以上的參數(shù)解析方法。
2.參數(shù)類型是Map
2.參數(shù)類型是Model
七、自定義對(duì)象參數(shù)
1.使用
八、自定義對(duì)象類型參數(shù)封裝原理
1.還是同樣會(huì)進(jìn)入到以上的參數(shù)解析方法。
2.ServletModelAttributeMethodProcessor 參數(shù)處理器
3.Converters-數(shù)據(jù)類型轉(zhuǎn)換器
4.自定義Converter類型轉(zhuǎn)換器
一、參數(shù)注解
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody、@RequestAttribute
import org.springframework.web.bind.annotation.*; import javax.servlet.http.Cookie; import java.util.HashMap; import java.util.List; import java.util.Map;@RestController public class ParameterTestController {/*** 數(shù)據(jù)綁定:頁(yè)面提交的請(qǐng)求數(shù)據(jù)(GET、POST)都可以和對(duì)象屬性進(jìn)行綁定* @param person* @return*/@PostMapping("/saveuser")public Person saveuser(Person person){return person;}// car/2/owner/zhangsan@GetMapping("/car/{id}/owner/{username}")public Map<String,Object> getCar(@PathVariable("id") Integer id,@PathVariable("username") String name,@PathVariable Map<String,String> pv,@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> header,@RequestParam("age") Integer age,@RequestParam("inters") List<String> inters,@RequestParam Map<String,String> params,@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie){Map<String,Object> map = new HashMap<>();// map.put("id",id); // map.put("name",name); // map.put("pv",pv); // map.put("userAgent",userAgent); // map.put("headers",header);map.put("age",age);map.put("inters",inters);map.put("params",params);map.put("_ga",_ga);System.out.println(cookie.getName()+"===>"+cookie.getValue());return map;}@PostMapping("/save")public Map postMethod(@RequestBody String content){Map<String,Object> map = new HashMap<>();map.put("content",content);return map;}}1.@PathVariable
(1) 獲取路徑變量的值。
@PathVariable("id") Integer id
@PathVariable("username") String name
(2)?獲取所有的路徑變量,必須使用Map<String,String>
@PathVariable Map<String,String> pv
2.@RequestHeader
(1)?獲取請(qǐng)求頭
@RequestHeader("User-Agent") String userAgent
(2)?獲取所有請(qǐng)求頭
@RequestHeader Map<String,String> header
3.@RequestParam
(1)?獲取請(qǐng)求參數(shù)xxxx?age=18&inters=games&inters=ball
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters,
(2)?獲取所有請(qǐng)求參數(shù)
@RequestParam Map<String,String> params
4.@CookieValue
(1)?獲取某個(gè)cookie的值
@CookieValue("_ga") String _ga
(2)?獲取Cookie類型的cookie
@CookieValue("_ga") Cookie cookie
5.@RequestBody
只能用于post方式。
(1) @RequestBody String content
獲取請(qǐng)求體的內(nèi)容
6.@RequestAttribute
獲取請(qǐng)求域中屬性的值。
(1)?第一種獲取轉(zhuǎn)發(fā)參數(shù):
@RequestAttribute(value = "msg",required = false) String msg
@RequestAttribute(value = "code",required = false)Integer code
(2)?第二種獲取轉(zhuǎn)發(fā)參數(shù)
request.getAttribute("msg");
(3)?注意:
@RequestAttribute不可以用Map獲取所有的請(qǐng)求域的值。
Attribute更多用于設(shè)置值之后跳轉(zhuǎn)頁(yè)面,用el表達(dá)式獲取值。
@GetMapping("/goto") public String goToPage(HttpServletRequest request){request.setAttribute("msg","成功了...");request.setAttribute("code",200);return "forward:/success"; //轉(zhuǎn)發(fā)到 /success請(qǐng)求 }@ResponseBody @GetMapping("/success") public Map success(@RequestAttribute(value = "msg",required = false) String msg,@RequestAttribute(value = "code",required = false)Integer code,HttpServletRequest request){Object msg1 = request.getAttribute("msg");Map<String,Object> map = new HashMap<>();Object hello = request.getAttribute("hello");Object world = request.getAttribute("world");Object message = request.getAttribute("message");map.put("reqMethod_msg",msg1);map.put("annotation_msg",msg);map.put("hello",hello);map.put("world",world);map.put("message",message);return map; }7.@MatrixVariable
矩陣變量
(1)?語(yǔ)法:?
請(qǐng)求路徑:/cars/sell;low=34;brand=byd,audi,yd
請(qǐng)求路徑:/cars/sell;low=34;brand=byd;brand=audi;brand=yd
請(qǐng)求路徑:/boss/1;age=20/2;age=10
(2)?使用
* SpringBoot默認(rèn)是禁用了矩陣變量的功能
* 手動(dòng)開啟:原理。對(duì)于路徑的處理。UrlPathHelper進(jìn)行解析。removeSemicolonContent(移除分號(hào)內(nèi)容)支持矩陣變量的。
*?矩陣變量必須有url路徑變量才能被解析。
(3)?開啟方式
①?第一種,重寫匹配規(guī)則
@Configuration(proxyBeanMethods = false) public class WebConfig implements WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 設(shè)置為不移除分號(hào)“;”后面的內(nèi)容。矩陣變量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);} }②?第二種,直接在容器中添加組件
@Bean public WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 設(shè)置為不移除分號(hào)“;”后面的內(nèi)容。矩陣變量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}}; }(4)?使用代碼
// /cars/sell;low=34;brand=byd,audi,yd @GetMapping("/cars/{path}") public Map carsSell(@MatrixVariable("low") Integer low,@MatrixVariable("brand") List<String> brand,@PathVariable("path") String path){Map<String,Object> map = new HashMap<>();map.put("low",low); // 34map.put("brand",brand); // byd,audi,ydmap.put("path",path); // sellreturn map; }// /boss/1;age=20/2;age=10 @GetMapping("/boss/{bossId}/{empId}") public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){Map<String,Object> map = new HashMap<>();map.put("bossAge",bossAge); // 20map.put("empAge",empAge); // 10return map; }(5) 問題:做頁(yè)面開發(fā),如果cookie被禁用了,如何獲取session?
解析:session是由每次請(qǐng)求攜帶的cookie中的jsessionid定位的,cookie被禁用了,就無法通過cookie查找到對(duì)應(yīng)的session。
解決:使用矩陣變量,發(fā)起請(qǐng)求/xxx;jsessionid=xxxxx,把cookie的值使用矩陣變量的方式進(jìn)行傳遞。
二、參數(shù)解析原理
springmvc處理邏輯參考博文:
springBoot-springMVC請(qǐng)求處理原理_A_art_xiang的博客-CSDN博客
參數(shù)解析也是從DispatcherServlet類的doDispatch方法開始研究。
1.DispatcherServlet類的doDispatch方法
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);// 找@RequestMapping注解注釋的方法,HandlerMapping中找到能處理請(qǐng)求的Handler(Controller.method())// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 為當(dāng)前Handler 找一個(gè)適配器 HandlerAdapter; RequestMappingHandlerAdapter// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.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;}// 真正的執(zhí)行目標(biāo)方法// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);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);}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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}} }2.HandlerAdapter
有四種處理器的適配器
?
RequestMappingHandlerAdapter支持方法上標(biāo)注@RequestMapping。
HandlerFunctionAdapter支持函數(shù)式編程的。
等等 。
// org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter 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"); }3.執(zhí)行目標(biāo)方法
// Actually invoke the handler. //DispatcherServlet -- doDispatch中的 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 以上進(jìn)去之后實(shí)際是執(zhí)行的RequestMappingHandlerAdapter的handleInternal方法 // RequestMappingHandlerAdapter的handleInternal方法 @Override protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;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 necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// 真正的執(zhí)行目標(biāo)方法// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav; } // RequestMappingHandlerAdapter的invokeHandlerMethod方法 @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);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// 注冊(cè)參數(shù)解析器-argumentResolvers,有26個(gè)。if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// 注冊(cè)返回值處理器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()) {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);}// 真正執(zhí)行目標(biāo)方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();} } // ServletInvocableHandlerMethod的invokeAndHandle方法 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 執(zhí)行請(qǐng)求,真正的執(zhí)行controller的目標(biāo)方法Object returnValue = 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;} } // InvocableHandlerMethod的invokeForRequest方法 @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 獲取方法所有參數(shù)的值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 調(diào)用目標(biāo)方法return doInvoke(args); } // InvocableHandlerMethod的getMethodArgumentValues方法,這才是如何確定目標(biāo)方法每一個(gè)參數(shù)的值 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 獲取所有參數(shù)MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];// 遍歷所有參數(shù)for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs); // 參數(shù)名字發(fā)現(xiàn)器if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) { // 判斷所有26個(gè)參數(shù)解析器是否支持該參數(shù)類型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; } // HandlerMethodArgumentResolverComposite的getArgumentResolver方法,挨個(gè)判斷所有參數(shù)解析器那個(gè)支持解析這個(gè)參數(shù) @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); // 緩存,第一次執(zhí)行可能會(huì)慢,以后就會(huì)很快。break;}}}return result; }4.參數(shù)解析器-HandlerMethodArgumentResolver
確定將要執(zhí)行的目標(biāo)方法的每一個(gè)參數(shù)的值是什么;
SpringMVC目標(biāo)方法能寫多少種參數(shù)類型。取決于參數(shù)解析器。
(1)26個(gè)參數(shù)解析器
?(2)參數(shù)解析器實(shí)際是個(gè)接口
?
*?判斷當(dāng)前解析器是否支持解析這種參數(shù)
* 支持就調(diào)用 resolveArgument
最終參數(shù)解析時(shí),調(diào)用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
5.返回值處理器
有以下返回值可寫:
?
三、使用Servlet API作為參數(shù)
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
1.使用
@GetMapping("/test") public String test(HttpServletRequest request){return "success"; }四、使用Servlet API作為參數(shù)原理
1.InvocableHandlerMethod的getMethodArgumentValues方法
同上,也會(huì)來到InvocableHandlerMethod的getMethodArgumentValues方法,這才是如何確定目標(biāo)方法每一個(gè)參數(shù)的值
// InvocableHandlerMethod的getMethodArgumentValues方法,這才是如何確定目標(biāo)方法每一個(gè)參數(shù)的值 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 獲取所有參數(shù)MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];// 遍歷所有參數(shù)for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs); // 參數(shù)名字發(fā)現(xiàn)器if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) { // 判斷所有26個(gè)參數(shù)解析器是否支持該參數(shù)類型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; } // HandlerMethodArgumentResolverComposite的getArgumentResolver方法,挨個(gè)判斷所有參數(shù)解析器那個(gè)支持解析這個(gè)參數(shù) @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { // 循環(huán)判斷所有26個(gè)參數(shù)解析器是否支持該參數(shù)類型if (resolver.supportsParameter(parameter)) { // 判斷是否支持解析result = resolver;this.argumentResolverCache.put(parameter, result); // 緩存,第一次執(zhí)行可能會(huì)慢,以后就會(huì)很快。break;}}}return result; }2.HttpServletRequest作為參數(shù)的參數(shù)處理器
ServletRequestMethodArgumentResolver?解析以上的部分參數(shù)(WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId)
@Override public boolean supportsParameter(MethodParameter parameter) {Class<?> paramType = parameter.getParameterType();return (WebRequest.class.isAssignableFrom(paramType) ||ServletRequest.class.isAssignableFrom(paramType) ||MultipartRequest.class.isAssignableFrom(paramType) ||HttpSession.class.isAssignableFrom(paramType) ||(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||Principal.class.isAssignableFrom(paramType) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType); }@Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Class<?> paramType = parameter.getParameterType();// WebRequest / NativeWebRequest / ServletWebRequestif (WebRequest.class.isAssignableFrom(paramType)) {if (!paramType.isInstance(webRequest)) {throw new IllegalStateException("Current request is not of type [" + paramType.getName() + "]: " + webRequest);}return webRequest;}// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequestif (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {return resolveNativeRequest(webRequest, paramType);}// HttpServletRequest required for all further argument typesreturn resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); }五、復(fù)雜參數(shù)的解析
Map、Model(map、model里面的數(shù)據(jù)會(huì)被放在request的請(qǐng)求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向攜帶數(shù)據(jù))、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
1.使用
@GetMapping("/params") public String testParam(Map<String,Object> map,Model model,HttpServletRequest request,HttpServletResponse response){map.put("hello","world666");model.addAttribute("world","hello666");request.setAttribute("message","HelloWorld");Cookie cookie = new Cookie("c1","v1");response.addCookie(cookie);return "forward:/success"; }@ResponseBody @GetMapping("/success") public Map success(HttpServletRequest request){Map<String,Object> map = new HashMap<>();Object hello = request.getAttribute("hello");Object world = request.getAttribute("world");Object message = request.getAttribute("message");map.put("hello",hello);map.put("world",world);map.put("message",message);return map; }Map<String,Object> map,??Model model, HttpServletRequest request 都是可以給request域中放數(shù)據(jù),可以使用request.getAttribute();獲取數(shù)據(jù),也可以在頁(yè)面使用el表達(dá)式獲取。
六、復(fù)雜參數(shù)解析原理
1.還是同樣會(huì)進(jìn)入到以上的參數(shù)解析方法。
2.參數(shù)類型是Map
會(huì)使用MapMethodProcessor參數(shù)解析器。
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {// 判斷是否用該參數(shù)解析器解析@Overridepublic boolean supportsParameter(MethodParameter parameter) {return Map.class.isAssignableFrom(parameter.getParameterType()) &¶meter.getParameterAnnotations().length == 0;}// 解析@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");return mavContainer.getModel(); // 返回一個(gè)BindingAwareModelMap,既是Model 也是Map。} } // ModelAndViewContainer的getModel方法 public ModelMap getModel() {if (useDefaultModel()) {return this.defaultModel;}else {if (this.redirectModel == null) {this.redirectModel = new ModelMap();}return this.redirectModel;} }?目標(biāo)方法執(zhí)行完成,將所有的數(shù)據(jù)都放在 ModelAndViewContainer;包含要去的頁(yè)面地址View。還包含Model數(shù)據(jù)。
@Nullable private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav; }處理派發(fā)結(jié)果:
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); -> renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);InternalResourceView:視圖解析流程 @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// 暴露模型作為請(qǐng)求域?qū)傩?/ 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);} } protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有數(shù)據(jù)遍歷挨個(gè)放在請(qǐng)求域中(在頁(yè)面跳轉(zhuǎn)之前)model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}}); }2.參數(shù)類型是Model
會(huì)使用ModelMethodProcessor參數(shù)解析器。
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return Model.class.isAssignableFrom(parameter.getParameterType());}@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");return mavContainer.getModel(); // 跟Map參數(shù)一樣,也返回一個(gè)BindingAwareModelMap,既是Model 也是Map。} }七、自定義對(duì)象參數(shù)
可以自動(dòng)類型轉(zhuǎn)換與格式化,可以級(jí)聯(lián)封裝
1.使用
/** * 姓名: <input name="userName"/> <br/> * 年齡: <input name="age"/> <br/> * 生日: <input name="birth"/> <br/> * 寵物姓名:<input name="pet.name"/><br/> * 寵物年齡:<input name="pet.age"/> */ @Data public class Person {private String userName;private Integer age;private Date birth;private Pet pet; }@Data public class Pet {private String name;private String age; } /** * 數(shù)據(jù)綁定:頁(yè)面提交的請(qǐng)求數(shù)據(jù)(GET、POST)都可以和對(duì)象屬性進(jìn)行綁定 */ @PostMapping("/saveuser") public Person saveuser(Person person){return person; }八、自定義對(duì)象類型參數(shù)封裝原理
1.還是同樣會(huì)進(jìn)入到以上的參數(shù)解析方法。
2.ServletModelAttributeMethodProcessor 參數(shù)處理器
自定義類型的參數(shù),是用ServletModelAttributeMethodProcessor 參數(shù)處理器解析的。
// ModelAttributeMethodProcessor的supportsParameter方法(ServletModelAttributeMethodProcessor繼承ModelAttributeMethodProcessor) @Override public boolean supportsParameter(MethodParameter parameter) {return (parameter.hasParameterAnnotation(ModelAttribute.class) ||(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));// 參數(shù)是不是簡(jiǎn)單類型,自定義對(duì)象不是簡(jiǎn)單類型 }// org.springframework.beans.BeanUtils#isSimpleProperty public static boolean isSimpleProperty(Class<?> type) {Assert.notNull(type, "'type' must not be null");return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType())); } public static boolean isSimpleValueType(Class<?> type) {return (Void.class != type && void.class != type &&(ClassUtils.isPrimitiveOrWrapper(type) ||Enum.class.isAssignableFrom(type) ||CharSequence.class.isAssignableFrom(type) ||Number.class.isAssignableFrom(type) ||Date.class.isAssignableFrom(type) ||Temporal.class.isAssignableFrom(type) ||URI.class == type ||URL.class == type ||Locale.class == type ||Class.class == type)); } // org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument @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 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 instancetry {attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {// No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (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.// attribute就是創(chuàng)建的空實(shí)體類對(duì)象(Person),webRequest就是原生的request請(qǐng)求。// WebDataBinder :web數(shù)據(jù)綁定器,將請(qǐng)求參數(shù)的值綁定到指定的JavaBean里面// WebDataBinder 利用它里面的 Converters 將請(qǐng)求數(shù)據(jù)轉(zhuǎn)成指定的數(shù)據(jù)類型。再次封裝到JavaBean中WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {// 關(guān)鍵一步,幫我們把請(qǐng)求里的數(shù)據(jù)綁定到Person對(duì)象,執(zhí)行的是ServletModelAttributeMethodProcessor類的方法。// 綁定過程中,有使用到Converter類型轉(zhuǎn)換器,因?yàn)閔ttp都是string,如果對(duì)象有Integer等類型需要進(jìn)行轉(zhuǎn)換。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.Optionalif (!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 modelMap<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute; }GenericConversionService類:在設(shè)置每一個(gè)值的時(shí)候,找它里面的所有converter那個(gè)可以將這個(gè)數(shù)據(jù)類型(request帶來參數(shù)的字符串)轉(zhuǎn)換到指定的類型(JavaBean -- Integer)(文件上傳:byte -- > file)
3.Converters-數(shù)據(jù)類型轉(zhuǎn)換器
WebDataBinder 利用它里面的 Converters 將請(qǐng)求數(shù)據(jù)轉(zhuǎn)成指定的數(shù)據(jù)類型。再次通過反射封裝到JavaBean中。
?
?
4.自定義Converter類型轉(zhuǎn)換器
未來我們可以給WebDataBinder里面放自己的Converter(T就是要轉(zhuǎn)換的類型);
private static final class StringToNumber<T extends Number> implements Converter<String, T>
(1)新需求:假設(shè)寵物輸入“貓,3”,就以“,”分割,前后參數(shù)分別綁定到Pet的name和age。
/** * 姓名: <input name="userName"/> <br/> * 年齡: <input name="age"/> <br/> * 生日: <input name="birth"/> <br/> * 寵物:<input name="pet"/> */ @Data public class Person {private String userName;private Integer age;private Date birth;private Pet pet; }@Data public class Pet {private String name;private String age; }(2)定義config類
import com.cxf.model.Pet; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration(proxyBeanMethods = false) public class WebConfig{//WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {// 添加一個(gè)String->Pet的類型轉(zhuǎn)換器registry.addConverter(new Converter<String, Pet>() {@Overridepublic Pet convert(String source) {// 貓,3if(!StringUtils.isEmpty(source)){Pet pet = new Pet();String[] split = source.split(",");pet.setName(split[0]);pet.setAge(Integer.parseInt(split[1]));return pet;}return null;}});}};} }(3)測(cè)試一下
http://localhost:8080/person?userName=zhangsan&age=18&birth=1992/12/12&pet=mao,3
{"userName":"zhangsan","age":18,"birth":"1992-12-11T16:00:00.000+00:00","pet":{"name":"mao","age":3}}
總結(jié)
以上是生活随笔為你收集整理的springboot-springmvc请求参数获取与原理【长文预警,收藏慢啃】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springBoot-springMVC
- 下一篇: springboot-异常处理使用与原理