# 7.SpringMVC-Restful风格

# 1.简介

它本质是一种设计 Web API 的风格 / 规范,不是技术,而是让接口更简洁、规范、易理解的一套约定。

Restful 是 REST(Representational State Transfer,表述性状态转移) 架构风格的实践规范


# 2.RESTFUL实现

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL 地址的一部分,以保证整体风格的一致性

操作 传统方式 REST 风格
查询操作 getUserById?id=1 user/1 --> GET 请求方式
保存操作 saveUser user --> POST 请求方式
删除操作 deleteUser?id=1 user/1 --> DELETE 请求方式
更新操作 updateUser user --> PUT 请求方式

# 3.HiddenHttpMethodFilter

# 步骤 1:过滤器初始化(静态代码块)

类加载时,静态代码块先执行,初始化允许转换的方法列表:

// ALLOWED_METHODS = [PUT, DELETE, PATCH](不可修改)
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
1
2

# 步骤 2:过滤器拦截请求(doFilterInternal 入口)

请求进入 doFilterInternal 方法,这是过滤器的核心处理逻辑:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    HttpServletRequest requestToUse = request; // 初始化请求对象为原始请求
    // 步骤3:判断是否是POST请求,且无异常
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
        // 步骤4:获取隐藏参数 _method 的值
        String paramValue = request.getParameter(this.methodParam); // paramValue = "PUT"
        // 步骤5:判断参数是否非空
        if (StringUtils.hasLength(paramValue)) {
            // 步骤6:参数转大写(统一格式)
            String method = paramValue.toUpperCase(Locale.ENGLISH); // method = "PUT"
            // 步骤7:判断是否是允许转换的方法(PUT在ALLOWED_METHODS中)
            if (ALLOWED_METHODS.contains(method)) {
                // 步骤8:包装请求,替换请求方法为PUT
                requestToUse = new HttpMethodRequestWrapper(request, method);
            }
        }
    }
    // 步骤9:放行包装后的请求
    filterChain.doFilter((ServletRequest)requestToUse, response);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 步骤 3:核心判断(是否满足转换条件)

过滤器首先校验两个核心条件:

  1. 原始请求方法是 POST(前端只能发 POST);

  2. 请求中没有异常属性(排除错误请求);

    → 满足条件,进入下一步。

# 步骤 4:获取并校验隐藏参数

  1. 从请求中获取 _method 参数的值(默认参数名,可通过 setMethodParam 修改);
  2. 前端传的是 PUT,所以 paramValue = "PUT"
  3. 校验参数非空(StringUtils.hasLength(paramValue) 返回 true);
  4. 参数转大写(避免大小写问题,如 putPUT);
  5. 校验 PUT 是否在允许的方法列表中(ALLOWED_METHODS 包含 PUT,返回 true)。

method参数,由前端的hidden输入表单提交过来

# 步骤 5:包装请求(核心:替换请求方法)

创建 HttpMethodRequestWrapper(请求包装类),这是关键:

private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    private final String method; // 存储新的请求方法:PUT

    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
        super(request); // 继承原始请求的所有属性
        this.method = method; // 赋值为PUT
    }

    // 重写getMethod方法,返回包装后的方法(PUT)
    public String getMethod() {
        return this.method;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

→ 这个包装类的核心作用是:当后续代码调用 request.getMethod() 时,返回的是 PUT 而非原始的 POST

# 步骤 6:放行请求,后续处理

过滤器将包装后的 requestToUse(方法为 PUT)放行到过滤器链:

  1. 后续的 SpringMVC 前端控制器(DispatcherServlet)接收请求;
  2. DispatcherServlet 调用 request.getMethod(),得到的是 PUT
  3. 根据 @PutMapping("/user/1") 匹配对应的控制器方法;
  4. 控制器方法按 PUT 请求的逻辑处理(如更新用户)。

# 4.实例

功能 URL 地址 请求方式
访问首页 / GET
查询全部数据 /employee GET
删除 /employee/2 DELETE
跳转到添加数据页面 /toAdd GET
执行保存 /employee POST
跳转到更新数据页面 /employee/2 GET
执行更新 /employee PUT

1、准备数据

官网下载vue.js放在webapp/static/js下

pojo

public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;

    public Employee() {
    }

    public Employee(Integer id, String lastName, String email, Integer gender) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }
}
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

dao

@Repository
public class EmployeeDao {

   private static Map<Integer, Employee> employees = null;
   
   static{
      employees = new HashMap<Integer, Employee>();

      employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
      employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
      employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
      employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
      employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
   }
   
   private static Integer initId = 1006;
   
   public void save(Employee employee){
      if(employee.getId() == null){
         employee.setId(initId++);
      }
      employees.put(employee.getId(), employee);
   }
   
   public Collection<Employee> getAll(){
      return employees.values();
   }
   
   public Employee get(Integer id){
      return employees.get(id);
   }
   
   public void delete(Integer id){
      employees.remove(id);
   }
}

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

2、实现功能

controller层

@GetMapping("/employee")
    public String getAllEmployees(Model model) {
        Collection<Employee> employeeList= employeeDao.getAll();
        model.addAttribute("employeeList",employeeList);
        return "employee_list";
    }

    @DeleteMapping("/employee/{id}")
    public String deleteEmployee(@PathVariable("id") Integer id){
        employeeDao.delete(id);
        return "redirect:/employee";
    }

    @PostMapping("/employee")
    public String addEmployee(Employee employee){
        employeeDao.save(employee);
        return "redirect:/employee";
    }

    @GetMapping("/employee/{id}")
    public String getEmployee(@PathVariable("id") Integer id,Model model){
        Employee employee = employeeDao.get(id);
        model.addAttribute("employee",employee);
        return "employee_update";
    }
    @PutMapping("/employee")
    public String updateEmployee(Employee employee){
        employeeDao.save(employee);
        return "redirect:/employee";
    }
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

配置xml

     <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    <mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
    <mvc:default-servlet-handler/>
    <!-- 核心:开启SpringMVC注解驱动(必须加) -->
    <mvc:annotation-driven/>
1
2
3
4
5

index

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a th:href="@{/employee}">查询员工信息</a>
</body>
</html>
1
2
3
4
5
6
7
8
9
10

employee_list

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table id="dataTable" border="1" cellspacing="0" cellpadding="0" style="text-align: center">
    <tr>
        <th colspan="5">员工信息</th>
    </tr>
    <tr>
        <th>id</th>
        <th>lastName</th>
        <th>email</th>
        <th>gender</th>
        <th>options<a th:href="@{/toAdd}">(add)</a> </th>
    </tr>
    <tr th:each="emp:${employeeList}">
        <td th:text="${emp.id}"></td>
        <td th:text="${emp.lastName}"></td>
        <td th:text="${emp.email}"></td>
        <td th:text="${emp.gender}"></td>
        <td>
            <a th:href="@{'/employee/'+${emp.id}}">编辑</a>
            <a @click="deleteEmployee" th:href="@{'/employee/'+${emp.id}}">删除</a>
        </td>
    </tr>
</table>
    <form id="deleteForm" method="post">
        <input type="hidden" name="_method" value="DELETE">
    </form>

    <script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
    <script type="text/javascript">
        var vue = new Vue({
            el:"#dataTable",
            methods:{
                //event表示当前事件
                deleteEmployee:function (event) {
                    //通过id获取表单标签
                    var deleteForm = document.getElementById("deleteForm");
                    //将触发事件的超链接的href属性为表单的action属性赋值
                    deleteForm.action = event.target.href;
                    //提交表单
                    deleteForm.submit();
                    //阻止超链接的默认跳转行为
                    event.preventDefault();
                }
            }
        });
    </script>



</body>
</html>
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

add

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
    lastName: <input type="text" name="lastName">
    email: <input type="text" name="email">
    gender: <input type="radio" name="gender" value="1"><input type="radio" name="gender" value="0"></input>
    <input type="submit" value="add">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

update

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="hidden" name="id" th:value="${employee.id}">
    lastName: <input type="text" name="lastName" th:value="${employee.lastName}">
    email: <input type="text" name="email" th:value="${employee.email}">
<!--    th:field="${employee.gender}"可用于单选框或复选框的回显
        若单选框的value和employee.gender的值一致,则添加checked="checked"属性-->
    gender: <input type="radio" name="gender" value="1" th:field="${employee.gender}"><input type="radio" name="gender" value="0" th:field="${employee.gender}"></input>
    <input type="submit" value="update">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5.mvc:default-servlet-handler

上述示例中的删除操作,必须要配置静态资源vue.js,若想成功解析必须配置default-servlet-handler

核心:让 SpringMVC 处理不了的请求,交给 Servlet 容器的默认 Servlet 处理,解决静态资源(如 js、css、图片)无法访问的问题。

关键配合:必须和 <mvc:annotation-driven/> 一起用,否则动态接口失效;

本质:它是 SpringMVC 对 “Servlet 容器默认功能” 的兜底调用,避免 DispatcherServlet 接管所有请求后 “管不了静态资源”。


最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |