日常案例分析笔记
看案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Controller
@RequestMapping("demo")
public class ForwardDemoController {
@GetMapping("ct1")
public String ct1(Model model) {
model.addAttribute("name", "xxx");
model.addAttribute("age", 123);
System.out.println("ct1 executed...");
return " forward:/demo1/ct2 ";
}
// 可以接收 queryParamater 以及 formData x-www-form-urlencoded 类型的入参 (ServletModelAttributeMethodProcessor 处理)
@GetMapping("ct2")
@ResponseBody
public String ct2(Model model, HttpServletRequest request, P p) {
System.out.println(p.name);
System.out.println(p.age);
System.out.println(model); // 打印 {}
System.out.println(CKJSON.getInstance()
.toJsonString(request.getAttribute("name"))); // 打印 xxx
System.out.println("ct2 executed...");
return "ct2返回了一条数据";
}
//可以接受收json 类型的入参
@GetMapping("ct3")
@ResponseBody
public String ct3(Model model, HttpServletRequest request, @RequestBody P p) {
System.out.println(p.name);
System.out.println(p.age);
System.out.println(model); // 打印 {}
System.out.println(CKJSON.getInstance()
.toJsonString(request.getAttribute("name"))); // 打印 xxx
System.out.println("ct3 executed...");
return "ct3返回了一条数据";
}
@Data
public static class P {
String name;
String age;
}
}
小伙伴问我 在ct1 中定义的 model, 为什么 通过ct1 forward 的请求, 在 ct2 和 3 的入参 model 不能接收到
分析
乍一听这个需求, 感觉有道理, 因为在 ct1 中定义的 model, 是可以在 2 和 3 的 HttpServletRequest 中拿到对应的信息的, 但是 model 是空对象
通过分析得出: model 只是做 render 用, 并不是接收参数的 argument, 我觉得spring 是为了避免出现属性污染, 而不做 model 透传, 因为 ct1 ct2 ct3 可能会服务于不同的视图, 贸然的透传可能带来一些莫名覆盖变量问题
forward 和 redirect 的区别:
1
2
3
4
1.forward 是 servlet 的 RequestDisptcher 的 forward 方法, redirect 是 response 的重定向方法
2.forward 是服务器内部重定向, 客户端无感知, redirecrt 是客户端重新发起请求, 是 302 行为
3.forward 在 spring 内部, 属于一个线程
4.可以使用 RedirectAttribute 来透传 model 的参数, 原理是 flashMap, 具体可以看 RequestMappingHandlerAdapter: getModelAndView方法, 这个方法也是最终生成视图的一步
mvc 是怎么处理 post 请求的
通过分析源代码, spring 在获取对应的 handler 之后, 为此 handler 进行了参数绑定, 具体可参考 RequestMappingHandlerAdapter:644
这里有个值得注意的点就是: 它在 getDefaultArgumentResolvers之中, 添加了两次 ServletModelAttributeMethodProcessor
一次入参传 true, 也就是处理注解 ModelAttribute 注解的, 一次传 false, 用来处理 formData x-www-form-urlencoded 请求的
而 ServletModelAttributeMethodProcessor的 bindRequestParameters 方法为最终为入参绑定值得处理逻辑, 是 dataBinder 最终处理的值绑定
为什么 model 里的值经过 forward 会存在于 request 中
分析源代码: DispatcherServlet:1367 render方法, 存在一个逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
view=resolveViewName(viewName,mv.getModelInternal(),locale,request);
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
// 具体确定是由哪个 resolver 来处理 是 UrlBasedViewResolver的 createView 方法确定
// DispatcherServlet 的 initViewResolvers 通过配置文件, 来确定, 默认的视图解析器都有哪些
view.render(mv.getModelInternal(), request, response);
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);
}
});
}
通过viewResolver 来生成最终的 view 视图, 而后调用 view.render 来最终渲染, 查看 View 的实现类, 最终确定由 InternalResourceView 来完成最终的渲染, 此类在最开始, 就遍历的 model, 把其属性存到 request 中