SpringMVC REST 详解

目录

一、什么是REST?

1.REST简介:

2.为什么SpringMVC要支持REST?

3.REST和HTTP常见请求方式之间的关系:

4.REST的核心过滤器:HiddenHttpMethodFilter

二、REST入门案例

0.案例需求:

1.配置过滤器:

2.配置注解驱动和静态资源放行:

3.前端页面:

4.后端Handler:

5.运行测试:

Δ总结


一、什么是REST?

1.REST简介:

REST 全称是 Representational State Transfer(表现层状态转移),最早是由 HTTP 协议的作者之一 Roy Fielding 在他 2000 年的博士论文中提出。

在 REST 的世界里,网络上的所有事物(如一个用户、一张图片、一条订单)都可以被抽象为"资源 "。每个资源都有一个唯一的标识符(URI)。资源可以有多种展现形式,比如 JSON、XML 或 HTML,这就是所谓的"表现层"(Representation)。

当我们在浏览器中点击一个链接 或提交一个表单 时,就是在驱动服务器端资源的状态发生改变。

2.为什么SpringMVC要支持REST?

1) REST 风格的接口通常返回 JSON 格式,使得同一套后端接口 可以同时服务于 Web 端、iOS 和 Android 移动端,实现了"++前后端解耦++"。

2) 通过标准的路径(URL)和动词(HTTP Method),开发者一眼就能看出这个接口是查数据还是删数据,增强了代码的可读性和泛用性。

3.REST和HTTP常见请求方式之间的关系:

在 RESTful 架构中,我们通过不同的 HTTP 请求方式来表达对资源的操作语义:GET :获取资源(查);POST :新建资源(增);PUT :更新资源(改);DELETE:删除资源(删)。

但是,HTML的 form表单,其中的 method 属性原生仅支持 GET 方式 和 POST 方式**。这就意味着:**如果我们想在纯粹的 JSP 或 Thymeleaf 页面中通过点击一个按钮来发送 DELETE 或 PUT 请求,浏览器是做不到的,这显然阻碍了我们践行标准的 REST 规范。

既然浏览器只能发 POST,那我们就可以三十六计------"瞒天过海":在发送 POST 请求时,额外带上一个隐藏参数,告诉服务器:"虽然我发的是 POST,但请你把我当成 DELETE 来看待"。正好,SpringMVC 为我们提供了这样一个核心过滤器------HiddenHttpMethodFilter

4.REST的核心过滤器:HiddenHttpMethodFilter

HiddenHttpMethodFilter的逻辑其实非常简单:它会拦截请求,检查是否为 POST ,并寻找请求中是否包含一个名为 _method的特殊参数。如果存在,它就会通过包装(Wrapper)将原有的 POST 请求模拟成参数中指定的请求方式(如DELETE、PUT)。HiddenHttpMethodFilter的核心源码逻辑如下:

java 复制代码
// 截取自 Spring 源码:org.springframework.web.filter.HiddenHttpMethodFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    // 1. 获取原始请求,判断是否为 POST
    String method = request.getMethod();
    if ("POST".equalsIgnoreCase(method) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        
        // 2. 获取请求中定义的特殊参数,默认参数名是 "_method"
        String paramValue = request.getParameter(this.methodParam);
        
        // 3. 如果参数不为空(比如是 PUT、DELETE、PATCH)
        if (StringUtils.hasLength(paramValue)) {
            String methodToUse = paramValue.toUpperCase(Locale.ENGLISH);
            
            // 4. 校验是否为合法的 HTTP 方法名
            if (ALLOWED_METHODS.contains(methodToUse)) {
                // 5. 核心:通过 HttpMethodRequestWrapper 包装类,将原始请求"伪装"成新的方法
                request = new HttpMethodRequestWrapper(request, methodToUse);
            }
        }
    }

    // 6. 将请求传递给后续的过滤器或控制层
    filterChain.doFilter(request, response);
}

好嘞!理论铺垫完毕。既然我们知道了浏览器无法直接发送 DELETE 请求,也知道了 SpringMVC 是通过 HiddenHttpMethodFilter 来帮我们"移花接木"的,那么下面我们就通过一个具体的 Book 资源管理案例,来看看如何在代码中配置并使用它。


二、REST入门案例

0.案例需求:

我们将会编写一个BookHandler 类,用于实现对 书籍 的CRUD。

然后编写一个 rest.jsp 页面,通过简单的crud 来测试REST。

1.配置过滤器:

首先我们要在 web.xml 文件中配置 HiddenHttpMethodFilter ,这里up将过滤器配置在了最前面,web.xml 代码如下:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 配置HiddenHttpMethodFilter -->
    <!-- HiddenHttpMethodFilter 可以把 以post方式提交的delete和put请求进行转换. -->
    <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>

    <!-- ======================================================= -->

    <!-- 配置中央控制器 -->
    <servlet>
        <servlet-name>mvcDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 通过 contextConfigLocation 指定 DispatcherServlet -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvcDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
        <!--
            url-pattern == /, 表示所有的用户请求都会经过 DispatcherServlet, 且支持Rest风格。
        -->
    </servlet-mapping>
</web-app>

2.配置注解驱动和静态资源放行:

接着,我们要在Spring的配置文件中,为 SpringMVC 配置 ++<mvc:annotation-driven/>++ 来开启全套注解支持,配置注解驱动后,Spring 会自动帮我们注册一些核心组件,以支持 @RequestMapping,JSR-303 数据校验等处理现代 Web 请求的能力。

我们还要配置 ++<mvc:default-servlet-handler/>++ 实现静态资源放行,这个配置我们可以理解为是 "DispatcherServlet" 的辅助,专门处理它搞不定的 CSS、JS、图片等文件。配置这一行后,Spring 会注册一个 DefaultServletHttpRequestHandler,这样的话,当一个请求进来,Spring 发现没有对应的 Controller 能处理时,它会把这个请求转交给服务器(如 Tomcat)自带的默认 Servlet

需要注意的是,这两个配置往往是前后相随的,他们满足了用户既要用注解,又要加载静态资源 的需求**。**

applicationContext-mvc.xml 代码如下:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置要扫描的包 -->
    <context:component-scan base-package="com.cyan.web"/>

    <!-- 激活注解(开启 Spring MVC 注解驱动) -->   <!-- IMPORTANT -->
    <mvc:annotation-driven/>
    <!-- 配置静态资源放行 -->
    <mvc:default-servlet-handler/>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--
            视图解析器需要配置两个属性------prefix(前缀) 和 suffix(后缀),
            视图解析器收到Servlet的 "return "login_OK";" 时,会用prefix和suffix与返回的资源名拼接。
        -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

3.前端页面:

我们先来写一个简单的 rest.jsp 页面,这个页面的主体部分共分为四部分,分别用于测试REST风格的"查询"、"添加"、"删除"和"修改"。rest.jsp 代码如下:(注意看注释!)

html 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>rest</title>
    <style>
        table, td {
            border: cornflowerblue 2px solid;
            border-collapse: collapse;
            padding: 2px;
        }
    </style>
    <script type="text/javascript" src="./js/jquery-3.7.0.min.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#deleteBook").click(function () {        //id选择器
                var href = this.href;

                //将超链接的href属性的值  赋值给隐藏表单的action属性
                $("#hiddenForm").attr("action", href);
                $(":hidden").val("DELETE");

                //这里就是提交删除请求了
                $("#hiddenForm").submit();

                //告诉那个超链接, 不要提交了.
                return false;
            });
        })
    </script>
</head>
<body>
<!--
    1) GET请求的查询 和 POST请求的增加, 都是没有用到过滤器
    2) 删除和修改书籍, 就要用到过滤器了.
    2) 因为 默认情况下, 超链接发出的请求一定是 GET 请求, 假如我们想通过超链接发出一个DELETE请求,
        此时就要用到过滤器的 public static final String DEFAULT_METHOD_PARAM = "_method";字段
 -->
    <h2 style="color: cornflowerblue">【Rest 测试】</h2> <br/>
    <hr>
    <h3 style="color: darkcyan">Rest 风格的url 查询书籍[get]</h3>
    <a href="book/search/141">点击查询书籍</a> <br/>
    <hr>

    <h3 style="color: darkcyan">Rest 风格的url 添加书籍[post]</h3>
    <form action="book/add" method="post">
        <table>
            <tr>
                <td>name:</td>
                <td><input name="bookName" type="text"></td>
            </tr>
            <tr>
                <td><input type="submit" value="添加书籍"></td>
                <td><input type="reset" value="重置输入"></td>
            </tr>
        </table>
    </form> <br/>
    <hr>

    <h3 style="color: darkcyan">Rest 风格的url 删除书籍[delete]</h3>
    <!--
        1) 这里指定的 _method 就是用到了 HiddenHttpMethodFilter 的
        public static final String DEFAULT_METHOD_PARAM = "_method";字段。
        过滤器后台就是按这个名字来获取hidden域的值, 从而进行请求转换的.

        2) 但是要用HiddenHttpMethodFilter的_method字段, 就必须以POST来发出请求. (源码要求)
            只有以POST发出请求, 过滤器才能捕获并转换为我们需要的DELETE请求方式.

        3) 具体来说, HiddenHttpMethodFilter支持转换三种不同的请求,分别是PUT,DELETE,和PATCH.
            源码如下:
            private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

        4) 整体思路是:为了实现REST风格的请求,
                       借助JQuery, 将原本是超链接发出的请求 替换为 隐藏表单的提交.
    -->
    <a href="book/delete/233" id="deleteBook">删除指定 id 的书籍</a>
    <%-- 隐藏的表单 --%>
    <form action ="" method="post" id="hiddenForm">
        <input type ="hidden" name="_method"/>
    </form> <br/>

    <hr>
    <h3 style="color: darkcyan">Rest 风格的url 修改书籍[put]</h3>
    <form action="book/update/{77}" method="post">
        <input type="hidden" name="_method" value="PUT">
        <input type="submit" value="修改书籍">
    </form>
</body>
</html>

rest.jsp页面的效果如下:(四个绿色的小标题表示四个不同的测试部分)

注意,由于 rest.jsp 中用到了JQuery,所以 up 这里在IDEA中引入的JQuery,如下图所示:

再来写一个简单的提示操作成功的页面,success.jsp 代码如下:

html 复制代码
<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神牛逼</title>
</head>
<body>
    <h2>Operation Successful!</h2>
</body>
</html>

4.后端Handler:

对应的 BookHandler类 代码如下:(注意看注释!)

java 复制代码
package com.cyan.web.rest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */

@RequestMapping(value = "/book")
@Controller
public class BookHandler {
    // 实现 查询 书籍的方法
    @GetMapping(value = "/search/{id}")
    public String searchBook(@PathVariable("id") String id) {
        System.out.println("查询id = " + id + "的书籍");

        return "success";
    }

    // 实现 添加 书籍的方法
    @PostMapping(value = "/add")
    public String addBook(String bookName) {
        //因为 BookName 是从表单直接提交过来的, 所以这里不需要配置@PathVariable.
        System.out.println("添加name = " + bookName + "的书籍");

        return "success";
    }

    // 实现 删除 书籍的方法
    @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
    public String deleteBook(@PathVariable("id") String bookId) {
        System.out.println("删除bookId = " + bookId + "的书籍");

        //return "success";  //直接这么写会报错:JSP 只允许 GET、POST 或 HEAD
        return "redirect:/book/success";    //重定向到下面那个方法
        /*
            这里 redirect 的第一个斜杠/ 会在服务器端被解析成 /Web工程名/, 然后拼接后面的
            然后浏览器访问重定向的路径时, 拼接后的第一个斜杠/ 再被浏览器解析为
            http://IP[域名]:port/, 然后拼接后面的, 就是完整的正确路径.
         */
    }

    @RequestMapping(value = "/success")
    public String successMsg() {
        return "success";
    }

    // 实现 修改 书籍的方法
    @RequestMapping(value = "/update/{id}", method = RequestMethod.PUT)
    public String updateBook(@PathVariable("id") String bookId) {
        System.out.println("修改bookId = " + bookId + "的书籍~~~");

        return "redirect:/book/success";    //重定向到successMsg方法
    }
}

5.运行测试:

运行效果如下 GIF图所示:


Δ总结

  • 🆗,以上就是本篇博文的全部内容了,感谢阅读!
  • 在SpringMVC中实现 RESTful 风格的URL,核心就是 Spring 的HiddenHttpMethodFilter 过滤器。推荐大家把我上面这个案例自己写一下,自己动手,收获多多。

System.out.println("END-----------------------------------------------------------------");

相关推荐
skilllite作者6 小时前
Warp 新手极速上手与部署指南
java·前端·笔记·安全·agentskills
许彰午6 小时前
我手写了一个 Java 内存数据库(四):索引引擎、SQL 解析与总结
java·数据库·sql
TO_ZRG6 小时前
Android Broadcast Receiver完全入门指南
java·后端·spring
Knight_AL6 小时前
使用 CyclicBarrier + 自定义线程池实现 SpringBoot 并行报表(完整性能对比)
java·spring boot·后端
人道领域7 小时前
【LeetCode刷题日记】347.前k个高频元素
java·数据结构·算法·leetcode
tjl521314_217 小时前
02C++ 静态变量与链接性
java·jvm·c++
摇滚侠7 小时前
Public Key Retrieval is not allowed
java·数据库·mysql
计算机学姐7 小时前
基于微信小程序的宠物服务系统【uniapp+springboot+vue】
java·vue.js·spring boot·mysql·微信小程序·uni-app·宠物
lst04267 小时前
Maven 构建命令
java·maven