12.17 Spring MVC 运行流程

  1. 所有请求,前端控制器(DispatcherServlet)收到请求,调用doDispatch进行处理
  2. 根据HandlerMapping中保存的请求映射信息找到,处理当前请求的处理器执行连
  3. 根据当前处理器找到他的HandlerAdapter
  4. 拦截器preHandle先执行
  5. 适配器执行目标方法(返回ModelAndView)
    1. ModelAttribute注解标注的方法提前运行
    2. 执行目标方法的时候 确定目标方法用的参数
    3. 有注解:
    4. 没有注解 1. 看是否Model Map以及其他的 2. 如果是自定义类型 再看是否有SessionAttribute标注的属性,如果是Session中拿 拿不到就抛异常 3. 都不是的话利用反射创建对象
  6. 拦截器的postHandle执行
  7. 处理结果(页面渲染流程)
    1. 如果有异常 使用异常解析器处理异常,处理完还返回ModelAndView
    2. 调用render进行页面渲染

多数都在do dispatch里面转来转去

12.16 视图解析器

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
@Controller
public class ViewResolver {
//方法1 相对路径法
@RequestMapping("/Hello")
public String hello(){
return "../../hello";//相对路径的形式访问hello.jsp
}
//方法2
@RequestMapping("/Hello01")
public String hello01(){
return "forward:/hello.jsp";//相对路径的形式访问hello.jsp
}
//方法3 把请求转发到handle01
@RequestMapping("/Hello02")
public String hello02(){
return "forward:/Hello01";
}
//forward:前缀的转发 不会由视图解析器品串

//重定向redirect:+重定向的路径
@RequestMapping("/hello03")
public String hello03(){
return "redirect:/hello.jsp";
}
}

12.16 接受请求参数的值

默认方式

直接给方法入参上写一个和请求参数名相同的变量,这个接收请求参数:有值就是有值/ 没值 就是null

1
2
3
4
5
6
7
@RequestMapping("/handle")
public String handle (String username, Model model){
System.out.println(username);
model.addAttribute("name",username);
model.addAttribute("time",System.currentTimeMillis());
return "success";
}
1
2
3
4
5
6
7
@RequestMapping("/handle01")
public String handle01 (@RequestParam(value = "user",required = false,defaultValue = "未指定默认值")String username, Model model){
System.out.println(username);
model.addAttribute("name",username);
model.addAttribute("time",System.currentTimeMillis());
return "success";
}

@RequestParam(“user”)有三个参数:

  • value 指定要获取的参数key

  • required 这个参数是否是必须的

  • defaultValue 默认值

@RequestHeader:获取请求头中的值

- 想获取哪个请求头就获取哪一个 当然是必须存在的,如果不存在的话会报500
1
2
3
4
5
6
7
@RequestMapping("/handle02")
public String handle02 (@RequestHeader(value = "User-agent") String userAgent, Model model){
System.out.println(userAgent);
model.addAttribute("name",userAgent);
model.addAttribute("time",System.currentTimeMillis());
return "success";
}

@CookieValue:获取某个Cookie的值

1
2
3
4
5
6
7
@RequestMapping("/handle03")
public String handle03 (@CookieValue(value = "JSESSIONID") String jid, Model model){
System.out.println(jid);
model.addAttribute("name",jid);
model.addAttribute("time",System.currentTimeMillis());
return "success";
}

3B0ACA55D0E6F5B9E98C79133A8E2DB0

12.16 中文乱码 字符编码转换

POST

jsp下web和java前后端交互会产生乱码,需要用CharacterEncodingFilter解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--    这个顺序会影响转换结果,放在最后约等于没用! 所以字符转换必须放在所有servlet等的最前面!-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>

</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

jsp页面上写点东西

1
2
3
4
5
6
7
8
9
<hr>
<form action="${pageContext.request.contextPath}/book" method="post">
书名<input type="text" name="bookName"><br>
作者<input type="text" name="author"><br>
价格<input type="text" name="price"><br>
库存<input type="text" name="stock"><br>
销量<input type="text" name="sales"><br>
<input type="submit" value="提交图书">
</form>

GET

在server.xml下找到

<

12.16 sessionAttribute为什么不要用

image-20191216171013985

这个等于向Spring承诺了,我的模型中会存在msg这个属性,那么如果没有的话 会报500错误的!

12.16 POJO 自动封装对象

POJO即自定义类型

如果我们请求参数是一个POJO 那么SpringMVC会自动为这个POJO赋值

首先我们有一个book,里面封装一个自定义的Address(内有city province)

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.runsstudio.springmvc.servlet;

public class Book {
private String bookName;
private String author;
private Double price;
private Integer stock;
private Integer sales;
private Address address;
public String getBookName() {
return bookName;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public Double getPrice() {
return price;
}

public void setPrice(Double price) {
this.price = price;
}

public Integer getStock() {
return stock;
}

public void setStock(Integer stock) {
this.stock = stock;
}

public Integer getSales() {
return sales;
}

public void setSales(Integer sales) {
this.sales = sales;
}

@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", author='" + author + '\'' +
", price=" + price +
", stock=" + stock +
", sales=" + sales +
", address=" + address +
'}';
}

public Book(String bookName, String author, Double price, Integer stock, Integer sales, Address address) {
this.bookName = bookName;
this.author = author;
this.price = price;
this.stock = stock;
this.sales = sales;
this.address = address;
}

public Book() {
}
}

然后我们有一个Controller

1
2
3
4
5
6
7
8
9
@Controller
public class BookController {

@RequestMapping("/book")
public String addBook(Book book, Model model){
model.addAttribute("book",book);
return "addSuccess";
}
}

jsp里写个表单

1
2
3
4
5
6
7
8
9
<form action="${pageContext.request.contextPath}/book" method="post">
书名<input type="text" name="bookName"><br>
作者<input type="text" name="author"><br>
价格<input type="text" name="price"><br>
库存<input type="text" name="stock"><br>
销量<input type="text" name="sales"><br>
地址:省份<input type="text" name="address.province">城市<input type="text" name="address.city"><br>
<input type="submit" value="提交图书">
</form>

提交表单

成功返回的页面

image-20191216151910175

注意:参数是区分大小写的 不能写bookname

参数类型如果不对应 比如把Double 输入了个String 会返回400 Bad Request

image-20191216170947176

12.16 MODEL MAP MODELMAP之间的关系

image-20191216155414285

一般来说 model这些东西 最经常是放在request域中

我们也可以放在其他域中

返回值是ModelAndView 可以为页面携带数据 但是也是默认在request中

如果想要在Session中保存的话

可以在类上加上一个注解: @SessionAttributes()

12.16 DispatcherServlet流程

image-20191216163546229

在每一个关键步骤都调用一个方法

12.16 @ModelAttribute(“”)注解

该注解告诉Spring ,这个数据是从数据库里面查到的,不是在开始的时候就由容器创建好的。

1
2
3
4
5
6
7
@RequestMapping("/book01")
public String addBook1(@ModelAttribute("book") Book book, Map<String,Object> map){
Book book=new Book(".....");//假设是从数据库里查到的
map.put("book",book);
System.out.println(book);
return "addSuccess";
}

这个注解的作用是防止在数据库UPDATE操作的时候,有的变量不应该重新赋值的时候,一些没有赋值的数据是null

12.13 REST思想

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
package com.runsstudio.springmvc.servlet;

public class RESTControlTest {
/**
* 万物皆资源 网站 上的资源无非就增删改查
* 发送请求 无非就是实现对资源的四种操作:增删改查
* HTTP协议里 有四个用来操作的请求方式
* GET 获取资源
* POST 新建资源
* PUT 更新
* DELETE 删除
* 我们希望的是 用一个URL 然后使用不同的请求方式 对应完成增删改查操作
* 这就是REST思想
* 系统希望以非常简洁的URL发请求
*/
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public String queryEmp(@PathVariable("id")String id, Model model){
System.out.println("GET:queryEmp,id="+id);
return "success";
}

@RequestMapping(value = "/{id}",method = RequestMethod.POST)
public String addEmp(@PathVariable("id")String id, Model model){
System.out.println("POST:addEmp,id="+id);
return "success";
}

}

/book/1 GET – 获取1号图书

/book/1 PUT – 更新1号图书

/book/1 DELETE – 删除1号图书

/book POST 添加图书


不幸的是 页面上只能发起GET和POST两种请求,其他的请求方式,没办法使用

那我们怎么办呢?

SPRING配置了一个hidden http method FILTER 可以弥补jsp页面不能发这两种请求的缺陷

  1. web.xml 拦截所有请求 首先配置hidden http method filter
1
2
3
4
5
6
7
8
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  1. 然后模拟增删改查操作 对于put和delete的 增加一个_method表单项 value 就是put method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%-- 模拟增删改查操作--%>
<form action="${pageContext.request.contextPath}/emp/1" method="get">
<input type="submit" value="提交GET">
</form>
<form action="${pageContext.request.contextPath}/emp/1" method="post">
<input type="submit" value="添加1号员工(POST)">
</form><br>
<%-- 要一个_method的项--%>
<form action="${pageContext.request.contextPath}/emp/1" method="post">
<input name="_method" value="put" >
<input type="submit" value="修改1号员工(PUT)">
</form><br>
<form action="${pageContext.request.contextPath}/emp/1" method="post">
<input name="_method" value="delete">
<input type="submit" value="删除1号员工(DELETE)">
</form>
  1. 然后正常写PUT 和delete的控制层代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 想要接受POST GET以外的其他请求,首先要创建一个filter
    * 然后要发起其他形式请求?
    * 1. 在页面创建一个POST类型的表单
    * 2. 表单项中携带一个_method参数
    * 3. _method的值就是DELETE 或PUT
    * 如果是高版本TOMCAT (8+)
    * @param id
    * @param model
    * @return
    */
    @RequestMapping(value = "/{id}",method = RequestMethod.PUT)
    public String updateEmp(@PathVariable("id")String id, Model model){
    System.out.println("PUT:updateEmp,id="+id);
    return "success";
    }
    @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    public String delEmp(@PathVariable("id")String id, Model model){
    System.out.println("DELETE:delEmp,id="+id);
    return "success";
    }
  2. 最后,对于TOMCAT 8以上的服务器 会限制只能发出GET和POST 请求 发出其他请求会报错,修改的方法:在success.jsp头上加isErrorPage=”true”

image-20191216102827830

1
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>