Model、Map 参数解析原理
# 250.Model、Map 参数解析原理
之前讲过,可以在方法中传入复杂参数:
- Map、Model(这两个类型的数据会被放在 request 的请求域 ,相当于调用了方法 request.setAttribute)
- Errors/BindingResult
- RedirectAttributes( 重定向携带数据)
- ServletResponse(response)
- SessionStatus
- UriComponentsBuilder
- ServletUriComponentsBuilder
现在就来讲讲其解析原理
# 新增方法
在 RequestController
类中新增 testParam
方法(方法中有 map 和 model 对象)
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
map.put("hello", "world666");
model.addAttribute("world", "hello");
request.setAttribute("message", "HelloWorld");
Cookie cookie = new Cookie("c1", "v1");
response.addCookie(cookie);
return "forward:/success"; //转发到 /success请求
}
2
3
4
5
6
7
8
9
10
11
12
修改 testForward 方法,获取 request 域中的参数(注意修改 msg 和 code 为非必输):
@ResponseBody
@GetMapping("/success")
public Map<String, Object> testForward(
@RequestAttribute(value = "msg" ,required = false) String msg,
@RequestAttribute(value = "code", required = false) Integer code,
HttpServletRequest request) {
Object hello = request.getAttribute("hello");
Object world = request.getAttribute("world");
Object message = request.getAttribute("message");
Map<String, Object> map = new java.util.HashMap<>();
map.put("reqMethod_msg", msg);
map.put("annotation_msg", request.getAttribute("msg"));
map.put("hello", hello);
map.put("world", world);
map.put("message", message);
return map;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
先不 debug,直接启动,访问 localhost: 8888/params (opens new window),能正常获取到数据,Cookie 也生效:
# map 类型的参数
由于我们是需要看参数解析器如何解析的,因此我们可以直接在 InvocableHandlerMethod
类上打断点:
然后我们执行到这里,步入进去:
然后我们再步入最后一行的方法,其源码如下:
@Override
@Nullable
public 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();
}
2
3
4
5
6
7
8
9
10
11
最后一行的变量 mavContainer,是 ModelAndViewContainer 类型的,其部分源码如下:
public class ModelAndViewContainer {
private final ModelMap defaultModel = new BindingAwareModelMap();
public ModelMap getModel() {
if (useDefaultModel()) {
return this.defaultModel;
}
else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
所以 map 类型的参数,会返回 BindingAwareModelMap
,继承关系如下:
public class BindingAwareModelMap extends ExtendedModelMap
↓
public class ExtendedModelMap extends ModelMap implements Model
↓
public class ModelMap extends LinkedHashMap<String, Object>
2
3
4
5
因此,BindingAwareModelMap 既是 Model 类型,也是 LinkedHashMap 类型
# Model 类型的参数
同理,我们继续 for 循环,直接步入到 InvocableHandlerMethod
类的 getMethodArgumentValues
方法 for 循环中,并步入:
可以看到其调用的是一模一样的方法:
返回后,我们看看 args 参数,发现其实是同一个对象!
也就是说,虽然我们传入了 map 和 model 参数,但其实都是一个对象。
接下来我们就来分析,他们是如何将数据放到 request 域中的
接下来我们继续 for 循环,直到所有参数都被解析了,并返回参数:
此时就会回到上一级,我们执行 doInvoke 方法:
然后就会执行我们自定义的处理器方法:
接下来我们在 ServletInvocableHandlerMethod
类的 invokeAndHandle
方法上打一个断点,并执行到断点处:这里就是处理返回结果的
步入后,我们再次步入到最后一行:
然后判断返回值是否字符串,是的话就保存到 mavContainer 中:
注意,此时处理器方法已经执行了,所有的数据都在 mavContainer 中,这是一个存放 ModelAndView 对象的容器;
接下来,就是处理该容器里的数据,放到 request 域中了。
我们一步步地步出,直到 DistpatchServlet 的类;然后我们步入 processDispatchResult
方法:这个方法就是放输入到 request 中的
步入该方法后,可以看到其继续调用了 render 方法,我们步入进去:
继续步入其 view.render 方法:
然后就到 render
exposeModelAsRequestAttributes
源码很简单:就是遍历 model,然后 setAttribute:
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);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
至此,设置完成。
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02