复杂参数Model、Map解析原理【用model、map操作请求参数】
早在Mvc阶段就知道,能用Model、Map存储数据然后在页面展示。现在探索的是拿到数据解析完参数,怎么把数据放在域中,展示在页面上。也就是如何完成渲染视图的过程的
控制器方法
这里把两个参数的required属性设置为不必须false(测试懒得改代码就要学会改属性),是因为在底下那个即将转发到这个控制器方法里面的请求内没有携带这两个名字的参数,required默认值是true,在访问请求时会报没有参数的错,所以设置为false
@ResponseBody
@RequestMapping("/requestParam")
public Map testRequestParam(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false) String code,
HttpServletRequest request){
Map map = new HashMap();
map.put("msg",msg);
map.put("code",code);
Object msg1 = request.getAttribute("msg");
System.out.println("msg1 = " + msg1);
Object baby = request.getAttribute("baby");
map.put("baby",baby);
Object model1 = request.getAttribute("model1");
map.put("model",model1);
Object req1 = request.getAttribute("req1");
map.put("req",req1);
return map;
}
@RequestMapping("/map")
public String testModel(Map<String,Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
map.put("baby","PaKe");
model.addAttribute("model1","Petter");
request.setAttribute("req1","value1");
Cookie cookie = new Cookie("k1","v1");
response.addCookie(cookie);
return "forward:/requestParam";
}
首先启动服务器,然后请求map,发现能够转发并通过request.getAttribute方法获取到存储在map、Model内的数据,这说明map、model存储的数据实际上在处理过程中被放在了request域中,即map、model会把数据放在request域中,然后通过reuqest对象操作获取,想知道为什么放进去、具体执行如何执行的
请求map,开始debug
首先来到DispatcherServlet中,确定处理请求控制器方法的适配器为RequestMappingXXXX,
然后来到mv = ha.handle....这一行处理参数解析器和执行控制器方法,进入
来到AbstractHandlerMethodAdapter--->>handle方法,再进入来到RequestMappingHandlerAdapter-->>invokeHandlerMethod,找到熟悉的
invocableMethod.invokeAndHandle(webRequest, mavContainer);
进入,来到熟悉的ServletInvocableHandlerMethod-->>invokeAndHandle里面找到
这里一行是处理请求、控制器参数,一行是设置返回结果,很重要
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
进入第一行,来这一句
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
再进入,来到处理控制器参数方法中,找到这两行
//确定参数解析器
if (!this.resolvers.supportsParameter(parameter)) {
......
//使用参数解析器具体解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
直接进第一行,首先就是从27个springboot支持的参数解析器中找一个能处理当前控制器参数的解析器
在这个for循环中,最终确定第一个参数是由MapMethodProcessor处理
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//先从缓存中获取,看有没有解析过这个参数的,这是重启服务器,所以没有,result==null,进入循环
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;
}
怎么处理?很简单
判断参数是不是map类型的就可以了
public boolean supportsParameter(MethodParameter parameter) {
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0);
}
上面确定好了参数解析器就会来到args[i]....这一行解析参数,解析参数就是从缓存中拿刚才匹配好的参数解析器,然后非空判断,然后resolveArgument解析返回数据
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//从缓存中拿刚才匹配到的参数解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
//解析器非空判断
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
//解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
上面确定第一个参数的参数解析器是MapMethodProcessor【控制器第一个参数就是Map类型的】,第二个参数是Model类型的,重复上面的步骤,确定他的解析器为ModelMethodProcessor,怎么确定?
一样是看参数类型是不是Model类型的,符合就是他处理。其他确定参数解析器的过程跟这两个也差不多
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
上面确定好了参数解析器,来到args[i]....这一行解析参数,最后返回结果。在观察方法getMethodArgumentValues返回结果时发现
Map和Model两个类型参数在进行解析处理后返回的是同一个类型ModelMap
处理完所有参数后,返回数组处理结果后,在这里可以发现参数,被解析为了ModelMap类型
在进入这个BindingAwareModelMap里面,发现它继承了ExtendedModelMap,在进入,发现前者继承了ModelMap,在进去,发现它继承了LinkedHashMap,再进去他就继承了HashMap。这说明ModelMap本质是Map,尽管这一点在Mvc学习时已经知道,但现在也忘得差不多了。而在ModelMap里面使参数变得既是Model也是Map【怎么变得先不管】
执行完这些后,来到Dispatcher中,找到这一行【这是渲染视图的,Mvc学过】
view.render(mv.getModelInternal(), request, response);
进入,来到AbstractView.render里面
这里面传进去了参数、request、response
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
这一行是关键,进入,来到AbstractView-->>createMergedOutputModel【创建合并输出的model数据】
找到这一行
上面已经把model、request、response传了进来,此时的model不为空,就把数据放到mergedModel里面
if (model != null) {
mergedModel.putAll(model);
}
最后返回mergedModel,来到render方法中,renderMergedOutputModel【渲染合并输出的模型数据】
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
在这里就是具体操作把参数放到请求域中,进入
来到了InternalResourceView--->>renderMergedOutputModel,这就是转发视图【Mvc中方法返回值加了forwad就会被解析为InternalResourceView】
// Expose the model object as request attributes.
//将模型数据作为请求属性公开--->>说白了就是这一句把数据放进了请求域中
exposeModelAsRequestAttributes(model, request);
进入,来到了AbstractView-->>exposeModelAsRequestAttributes里面
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
就是调用request.setAttribute(name, value);将参数放进去,超简单
也就是说,在处理个请求时执行的控制器方法中有Model、Map类型的参数时,这类参数会被解析为ModelAndMap,在控制器方法返回之后,并在DispatcherServlet执行到render【渲染视图】这一步【决定跳转之前(当前控制器方法返回值是有forward前缀的)】时将数据放进请求域中。
当通过下面那个setResponseStatus断点后,来到DispatcherServlet发现已经确定了要处理返回值的方法【上面控制器里面的另一个方法】,就是控制器中的另一个方法。它是怎么确定这个的先不管。经过和上面类似的操作确定参数解析器,对参数解析。返回解析结果,最后再来到DispatcherServlet里面
在HandlerMethodReturnValueHandlerComposite--->>handleReturnValue里面确定返回值
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
对返回值做非空判断,然后将结果返回
在上面发现他说View为空,所以肯定会有设置相应体在页面上显示的步骤,进入handleReturnValue这一行,来到RequestResponseBodyMethodProcessor-->>handleReturnValue
他就将返回值装进响应体作为响应报文显示在页面上
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
确定控制器方法返回值
最后在这一行返回数据和视图,他在RequestMappingHandlerAdapter--->>invokeHandlerMethod里面
return getModelAndView(mavContainer, modelFactory, webRequest);
最后将响应体和返回的model数据响应在页面上