本文共 13525 字,大约阅读时间需要 45 分钟。
目录
第七章 Web开发
7.3 请求参数处理
7.3.2 普通参数与基本注解
7.3.2.2 Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
- ServletRequestMethodArgumentResolver 以上的部分参数,比如HttpServletRequest request就是由这个参数解析器来进行解析的。
@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); }
7.3.2.3 复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes(重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
@GetMapping("/params")public String testParam(Mapmap, Model model, HttpServletRequest request, HttpServletResponse response){ //给map、model添加数据就相当于在request域中添加数据 map.put("hello","world"); model.addAttribute("world","hello"); request.setAttribute("message","hello world"); Cookie cookie = new Cookie("c1","v1"); response.addCookie(cookie); return "forward:/success";}//在"/success"的实现方法中可以通过request.getAttribute()方法来获取这些数据
- Map类型的参数解析器是MapMethodProcessor,Model类型的参数解析器是ModelMethodProcessor。都会返回 mavContainer.getModel();---> BindingAwareModelMap是Model也是Map。
- ModelAndViewContainer类下:
- 通过doDispatch()方法中对方法调用结束后的后续处理processDispatchResult方法将map和model的数据放到request域中。
7.3.2.4 自定义对象参数
可以把用户提交的数据,直接封装成自定义的对象。
/** * 姓名: * 年龄: * 生日: * 宠物姓名: * 宠物年龄: */@Datapublic class Person { private String userName; private Integer age; private Date birth; private Pet pet; }@Datapublic class Pet { private String name; private String age;}
@PostMapping("/saveuser")public Person saveuser(Person person) { return person;}
- 数据绑定:页面提交的请求数据(Get、Post)都可以和对象属性进行绑定。
7.3.3 POJO封装过程
参数解析器ServletModelAttributeMethodProcessor:解析自定义类型的参数。
- 先进入ModelAttributeMethodProcessor类内的supportsParameter方法判断是否是简单类型的参数
public static boolean isSimpleValueType(Class type) { return Void.class != type && Void.TYPE != 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);}
- 使用对应的resolver对参数进行处理。
- ModelAttributeMethodProcessor类内的resolveArgument方法调用ServletModelAttributeMethodProcessor的createAttribute方法,会先创建出一个空实例对象person。
- 后续就要给这个空实例对象封装请求传过来的值,即数据绑定过程
- ModelAttributeMethodProcessor类内的resolveArgument方法中的核心代码:
if (bindingResult == null) { //创建网页数据绑定器 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { //这一步就将请求里的值赋给了之前创建的空实例对象 this.bindRequestParameters(binder, webRequest); } this.validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } MapbindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute;
- 先创建一个web数据绑定器,将请求参数的值绑定到指定的JavaBean里面,而这个JavaBean就是attribute,即之前创建的空实例对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
-
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
-
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(如JavaBean的Integer类型),也有如何把字节流传换成文件byte -- > file
-
可以自定义Converters给WebDataBinder
-
private static final class StringToNumber<T extends Number> implements Converter<String, T>
泛型指的是把请求带来的String类型,转成自己想要的类型T
-
把请求中的数据绑定到对象上
bindRequestParameters(binder, webRequest);
7.3.3.1 自定义Converter转换器
假设此时不使用级联属性提交宠物,而是规定提交方式,逗号前是宠物名字,逗号后是宠物年龄
//1、WebMvcConfigurer定制化SpringMVC的功能 @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter() { @Override public Pet convert(String source) { // 啊猫,3 if(!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; } }); } }; }
7.3.4 参数处理原理
DispatcherServlet类中的方法doDispatcher是处理所有请求的起点:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //这一步就是之前说的找到了处理请求的映射规则,即哪个方法可以处理这个请求 //mappedHandler封标了目标方法的信息 mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //mappedHandler.getHandler()得到哪个方法可以处理这个请求 //找到处理当前方法适合的适配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //真正指定handle,即执行当前请求对应的方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } }}
- HandlerMapping中找到能处理请求的Handler(Controller.method(),控制器中的哪个方法来处理当前请求)
- 为当前Handle找一个适配器HandlerAdapter。RequestMappingHandlerAdapter
7.3.4.1 HandlerAdapter适配器
HandlerAdapter是SpringMVC底层设计的一个接口,里面有几个方法:
public interface HandlerAdapter { //是否支持处理这个handler boolean supports(Object handler); //支持的话就调用方法来处理 @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; /** @deprecated */ @Deprecated long getLastModified(HttpServletRequest request, Object handler);}
所有的HandlerAdapter如下:
- 0 - 支持方法上标注@RequestMapping
-
1 - 支持函数式编程的
遍历所有的HandlerAdapter,利用supports方法找到合适的那个HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { Iterator var2 = this.handlerAdapters.iterator(); //遍历寻找合适的适配器 while(var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); 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");}
7.3.4.2 执行目标方法
//DispatcherServlet--->doDispatch()
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//在类ServletInvocableHandlerMethod中//执行当前请求的目标方法,获得一个返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); //获取方法的参数值 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
7.3.4.3 参数解析器HandlerMethodArgumentResolver
HandlerMethodArgumentResolver确定将要执行的目标方法的每一个参数的值是什么。SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
参数解析器是一个接口:
- 先利用方法supportsParameter判断当前解析器是否支持解析这种参数
- 支持的话就调用方法resolveArgument
7.3.4.4 返回值处理器
决定目标方法能写多少种类型的返回值:
7.3.4.5 如何确定目标方法每一个参数的值
InvocableHandlerMethod类中的方法:
============InvocableHandlerMethod========================== 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; }
-
挨个判断所有参数解析器哪个支持解析这个参数
@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; }
-
解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
7.3.4.6 目标方法处理完成
将所有的数据都放在ModelAndViewContainer。包含要去的页面地址View,还包含Model数据。
7.3.4.7 处理派发结果
核心方法就是doDispatch()方法里的:
processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
- doDispatch()内的render方法相当于接下来要去哪个页面,开始渲染。
- 渲染新的视图,进入AbstractView下的render()方法:
-
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
-
进入InternalResourceView:内的renderMergedOutputModel方法:
-
暴露模型作为请求域属性
// Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request);
- 此时就可以验证上面的结论,参数map、model内的数据最后会存在request域中。并且这个过程是在渲染视图的过程中操作完成的。
PS:根据尚硅谷视频整理,如有侵权,联系删除
转载地址:https://blog.csdn.net/xxyneymar/article/details/122576182 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!