SpringBoot入门(五)请求参数处理(二)
发布日期:2022-03-04 11:48:25 浏览次数:1 分类:技术文章

本文共 11755 字,大约阅读时间需要 39 分钟。

目录


第七章 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(Map
   
     map,
    
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();
}
Map  bindingResultModel = 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:力扣题24两两交换链表中的节点
下一篇:SpringBoot入门(七)视图解析/Thymeleaf/拦截器/文件上传

发表评论

最新留言

网站不错 人气很旺了 加油
[***.36.148.248]2022年07月29日 08时06分28秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章

武汉大学计算机作业,武汉大学计算机学院研究生操作系统作业.docx 2020-01-10 11:49:31
小学计算机课打气球,小学信息技术课程中学生信息素养的培养.doc 2020-01-10 11:49:32
中职学生计算机学情分析报告,中职学生学情分析范文.doc 2020-01-10 11:49:32
计算机网络中对编码与调制,20132184_计算机网络_第三次作业_物理层程序.doc 2020-01-10 11:49:30
计算机控制系统考试题目,计算机控制系统复习试题答案.doc 2020-01-10 11:49:30
幼儿园ppt计算机考试试题,《幼儿园课件制作》期末试卷A卷 2020-01-10 11:49:30
计算机初始配置,怎么将电脑的网络设置初始化? 2020-01-10 11:49:30
利用计算机说话,科学网-刘瑞祥:怎样利用VBA或者VBS让计算机说话-孙冰的博文... 2020-01-10 11:49:30
char 类型与lpcwstr,类型“char *”的参数与类型“LPWSTR”的参数不兼容。 2020-01-10 11:49:31
尝试重新启动计算机和应用程序 错误4,win10开机提示登录组件错误4 请重新启动电脑管家怎么办... 2020-01-10 11:49:30
用计算机就能确定苹果手机吗,iPhone手机实用的7个隐藏功能,可惜很少人知道,你有用过吗?... 2020-01-10 11:49:30
科学计算机开方符号是什么意思,计算器里开平方的符号请举来瞧瞧,好让自己购 – 手机爱问... 2020-01-10 11:49:30
超级计算机配置有多高,Nvidia Tesla C1060 最便宜Tesla个人超级计算机配置 2020-01-10 11:49:30
台式台式计算机型号怎么看,台式电脑主板型号在哪里看 2020-01-10 11:49:30
下列属于计算机应用,计算机的应用领域可大致分为6个方面,下列选项中属于计算机应用领域的是... 2020-01-10 11:49:29
鸿蒙系统代还,三代还宗是啥?古代入赘有多可怜,李白:不是没办法谁会倒插门... 2020-01-10 11:49:29
华为mate40可以用鸿蒙系统吗,华为Mate40可升级鸿蒙系统,除了这点之外,还有哪些地方值得关注... 2020-01-10 11:49:29
福尔曼大学计算机排名,留学选校指南|纽约时报2019美国精英群体认可的大学榜单Top50... 2020-01-10 11:49:27
win7计算机sid,如何修改新萝卜家园win7系统电脑sid 2020-01-10 11:49:27
计算机老师实践课程名称,计算机教师 2020-01-10 11:49:27