• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

springmvc(八)RESTful风格

武飞扬头像
笑谈子云亭
帮助1

一、概念

1.1、REST

REST(Representational State Transfer,表述性状态转换) 描述了一个架构样式的网络系统,比如web应用。

它是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件,主要用户客户端和服务端交互软件。基于这个风格设计的软件可以更简洁更有层次,更易于实现缓存机制。

其核心价值在于如何设计出符合REST风格的接口。

1.2、RESTful

REST指的是一组架构设计原则和约束条件,满足这些约束条件和原则的应用程序或设计就是RESTful。

RESTful特点:

  • 资源Resources:互联网所有事物都可以抽象为资源,可以是文本、图像、视频、服务等,总之就是一个具体存在。可以使用URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问URI就可以,因此URI是每个资源的唯一识别符。
  • 表现层Representation:把资源具体呈现出来的形式,叫做它的表现层。比如文本可以用txt表示也可以用html、xml、json,甚至二进制表示。
  • 状态转换State Transfer:每发出一个请求,就代表客户端与服务端进行了一次交互。Http是一个无状态协议,即所有状态都保存在服务端。因此,客户端想要操作服务器,必须通过某种方式,让服务端发生状态转换State Transfer。这种状态转换建立在表现层,所以叫做表现层状态转换。
  • 具体来说,HTTP协议中有四种操作动词:GET、POST、PUT、DELETE,他们对应四种操作:GET获取资源,POST新建资源,PUT更新资源,DELETE删除资源。

以前操作资源方式:

  • http://localhost:8080/getExpress.do?id=1
  • http://localhost:8080/saveExpress.do
  • http://localhost:8080/updateExpress.do
  • http://localhost:8080/deleteExpress.do?id=1

使用RESTful操作资源:

  • GET /expresses #查询所有快递列表信息
  • GET /express/1001 #查询一个快递信息
  • POST /express #新建一个快递信息
  • PUT /express/1001 #更新一个快递信息
  • DELETE /express/1001 #删除一个快递信息

二、API/URL设计

2.1、动词 宾语

RESTful的核心思想就是客户端向服务端发出的指令是“动词 宾语”的结构,例如:GET /expresses这个指令,GET是动词,/expresses是宾语。

动词就是常用的5中HTTP方法,对应CRUD操作。

  • GET:Read(读取)
  • POST:Create(新建)
  • PUT:Update(更新)
  • PATCH:Update(部分更新)
  • DELETE:delete(删除)

PS:根据HTTP规范,动词一律大写。
一些代理只支持GET和POST方法,为了是这些有限方法支持RESTful API,需要一种方法覆盖原来的http方法。使用定制的HTTP头来X-HTTP-Method-Override来覆盖POST方法。

2.2、宾语必须是名词

宾语就是API的URL,是HTTP动词作用的对象,它应该是名词而不是动词。如:/expresses 这个 URL 就是正确的。

以下的URL都是不推荐的,因为带上了动词:
/getAllExpresses /getExpress /createExpress /deleteAllExpress

2.3、避免多级URL

如果资源中有多级分类,也不建议写多级URL,例如:获取球队中某个队员,
GET /team/1001/player/100

这种写法语义不明确,推荐使用查询字符串作为后缀:GET /team/1001?player=100

例如查询未取快递:

GET /expresses/statu 不推荐

GET /expresses?statu=false 推荐

三、HTTP状态码

客户端每次发出的请求,服务端都必须给出响应,响应包括HTTP状态码和数据两部分。

HTTP状态码是一个三位数,分成5个类别。5个大类包含100多种状态码,每一中状态码都有标准或者约定的解释,客户端只需要查看状态码,就可以知道发生了什么事情,所以服务器应该返回尽可能精准的状态码。

五类状态码如下:

  • 1xx: 相关信息
  • 2xx: 操作成功
  • 3xx: 重定向
  • 4xx: 客户端错误
  • 5xx: 服务端错误

API不需要1XX状态码,所以这个类别可以忽略。

3.1、状态码2xx

200状态码表示操作成功,但是不同的方法可以返回更加精准的状态码。

  • GET: 200 OK 表示一切正常
  • POST: 201 Created 表示新的资源已经创建成功
  • PUT: 200 OK
  • PATCH: 200 OK
  • DELETE:204 No Content 表示已经删除成功

3.2、状态码3XX

API用不到301状态码(永久重定向)和302状态码(暂时重定向),因为它们可以由应用级别返回,浏览器可以直接跳转,API不用考虑这两种情况。

API用到的3XX状态主要是303 See Other,表示参考另一个URL,它与“302”和“307”一样,表示“暂时重定向”,区别在于“302”和“307”用于GET请求,而303用于POST、PUT、DELETE。收到303后,浏览器不会自动跳转,而是让用户自己决定下一步怎么办。

  • 304:Not Modified 客户端使用缓存数据

3.3、状态码4XX

4XX状态码表示客户端错误。

  • 400 Bad Request:服务端不理解客户端请求,未做任何处理。
  • 401 UnAuthorized:用户未提供身份验证凭据,或者没有通过身份验证。
  • 403 Forbidden:用户通过了身份验证,但是不具有访问资源所需权限。
  • 404 Not Found:所请求资源不存在,或不可用。
  • 405 Method Not Allowed:用户已通过身份验证,但是所用的HTTP方法不在他的权限之内。
  • 410 Gone:所请求的资源已经从这个地址转移,不可再用。
  • 415 Unsupported Media Type:客户端要求的返回格式不支持,比如:API只能返回JSON格式,客户端需要XML格式。
  • 422 Unprocessable Entity:客户端上传的附件无法处理,导致请求失败。
  • 429 Too many Requests:客户端请求次数太多

3.4、状态码5XX

5XX状态码表示服务端错误。一般来说,API不会向客户端透露服务端详细信息,所以状态码较少。

  • 500 Internal Server Error:客户端请求有效,服务端内部处理出错了。
  • 503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

3.5、服务器响应

服务端响应一般不推荐使用文本,推荐使用json格式。所以服务端响应的HTTP头信息Content-Type属性设为application/json,客户端请求时,也要明确告诉服务端可以接受json格式,即请求的HTTP头ACCEPT属性也要设置为application/json。

四、案例

4.1、RESTful风格的查询

@Controller
public class RestfulController {

    private static List<Job> jobList = new ArrayList<>();

    static {
        for (int i = 0; i < 10; i  ) {
            jobList.add(new Job(i, "任务"   i, "描述信息", new Date()));
        }
    }

    /**
     * 查询所有任务
     *
     * @return
     */
    @RequestMapping(value = "jobs", method = RequestMethod.GET)
    @ResponseBody
    public List<Job> getAll() {
        System.out.println("RestfulController-------------------------getAll");
        return jobList;
    }

    /**
     * 查询单个任务
     * @param id
     * @return
     */
    @RequestMapping(value = "job/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Job getOne(@PathVariable("id") int id) {
        System.out.println("RestfulController-------------------------getOne");
        for (Job job : jobList) {
            if (job.getId() == id) {
                return job;
            }
        }
        return null;
    }

    /**
     * 任务操作页面跳转
     * @return
     */
    @RequestMapping("hello")
    public String hello() {
        return "jsp/restful";
    } 

}
学新通
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>restful</title>
    <script type="text/javascript" src="/js/jquery-1.11.1.js"></script>
    <script type="text/javascript">
        $(function () {
            //查询所有GET
            $("#btnGetAll").click(function () {
                $.ajax({
                    type:"GET",
                    url:"jobs",
                    data:"",
                    dataType:"json",
                    success:function (data) {
                        alert(JSON.stringify(data));
                        let html="";
                        $.each(data,function (i,d) {
                            html ="id:" d.id ",name:" d.name ",description:" d.description "<br/>";
                        })
                        $("#showResult").html(html);
                    }
                })
            });
            //查询单个GET
            $("#btnGetOne").click(function () {
                $.ajax({
                    type:"GET",
                    url:"job/" $("input[name='id']").val(),
                    data:"",
                    dataType:"json",
                    success:function (data) {
                        alert(JSON.stringify(data));
                        if(data==undefined||data=="")  $("#showResult").html("没有符合条件的数据");
                        $("#showResult").html("id:" data.id ",name:" data.name ",description:" data.description);
                    }
                })
            }); 
        });
    </script>
</head>
<body>
    <form id="myForm" action="" method="post">
        任务ID:<input type="text" name="id" />
        任务名称:<input type="text" name="name" />
        任务描述:<input type="text" name="description" />
        <br/><hr/>
        <button type="button" id="btnGetAll">查询所有GET</button>
        <button type="button" id="btnGetOne">查询单个GET</button>
        <button type="button" id="btnPost">添加POST</button>
        <button type="button" id="btnPut">更新PUT</button>
        <button type="button" id="btnDel">删除DELETE</button>
    </form>
    <p id="showResult">

    </p>
</body>
</html>
学新通

4.2、RESTful风格添加

/**
     * 任务添加
     * @param job
     * @return
     */
    @RequestMapping(value = "job",method = RequestMethod.POST)
    @ResponseBody
    public String add(Job job){
        System.out.println("RestfulController-------------------------add");
        jobList.add(job);
        return "201";
    } 
<script>
 //添加
    $("#btnPost").click(function () {
        $.ajax({
            type:"POST",
            url:"job",
            data:$("#myForm").serialize(),
            dataType:"json",
            success:function (data) {
                $("#showResult").html(data);
            }
        })
    });
 </script>

4.3、RESTful风格修改

 /**
     * 任务更新
     * @param id
     * @param jobData
     * @return
     */
    @RequestMapping(value = "job/{id}",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@PathVariable("id")int id,Job jobData){
        System.out.println(id);
        for(Job job:jobList){
            if(job.getId()==id){
                job.setName(jobData.getName());
                job.setDescription(jobData.getDescription());
                return "204";
            }
        }
        return "404";
    }
学新通
<script>
//更新
    $("#btnPut").click(function () {
        $.ajax({
            type:"POST",
            url:"job/" $("input[name='id']").val(),
            data:$("#myForm").serialize() "&_method=PUT",
            dataType:"json",
            success:function (data) {
                $("#showResult").html(data);
            }
        })
    });
 </script>

4.4、RESTful风格删除

/**
     * 任务删除
     * @param id
     * @return
     */
    @RequestMapping(value = "job/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String Delete(@PathVariable("id")int id){
        System.out.println(id);
        for(Job job:jobList){
            if(job.getId()==id){
                jobList.remove(job);
                return "204";
            }
        }
        return "500";
    }
学新通
<script>
 //删除
    $("#btnDel").click(function () {
        $.ajax({
            type:"POST",
            url:"job/" $("input[name='id']").val(),
            data:"_method=DELETE",
            dataType:"json",
            success:function (data) {
                $("#showResult").html(data);
            }
        })
    });
</script>

4.5、RESTful风格更新删除遇到的问题

在Ajax中,采用RESTful风格PUT和DELETE传递参数无效,传递到后台的参数为null。

产生原因在于Tomcat封装请求参数过程:

  1. 将请求体中的数据封装成为一个map;
  2. request.getParameter(key)会从这个map中取值;
  3. SpringMVC封装POJO对象时,会把POJO对象中每个属性的值进行request.getParameter();Ajax发送PUT或者DELETE请求时,请求体中数据通过request.getParameter()拿不到。Tomcat检测到时PUT或者DELETE请求时不会封装请求体中数据为map,只有POST请求才会封装为map。

解决方案:

1、在前端进行ajax请求时候在url参数中添加 &_method=PUT 或者 &_method=DELETE

学新通

2、在web.xml配置文件中添加配置:

 <!--  使用RESTful风格的URI 将页面普通的POST请求转为指定的DELETE或者PUT请求
         原理:在Ajax发送POST请求后,带_method参数,将其修改为DELETE或者PUT请求-->
    <filter>
        <filter-name>httpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>httpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

五、自定义响应结果

5.1、封装响应实体类

/**
 * @Author: JSONLiu
 * @Description: 响应结果实体
 * @Date Created in 2022-02-09 15:54
 * @Modified By:
 */
public class Result<T> {
    /**
     * 响应码
     */
    private Integer code;
    /**
     * 响应消息
     */
    private String message;
    /**
     * 响应数据
     */
    private T data;

    public Result() {
    }

    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result success(T data){
        return new Result(200,"ok",data);
    }

    public static <T> Result warn(String message){
        return new Result(404,message,null);
    }

    public static <T> Result success(Integer code,T data){
        return new Result(code,"ok",data);
    }

    public static <T> Result warn(Integer code,String message){
        return new Result(code,message,null);
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
学新通

5.2、修改控制器返回值

@Controller
public class RestfulController {

    private static List<Job> jobList = new ArrayList<>();

    static {
        for (int i = 0; i < 10; i  ) {
            jobList.add(new Job(i, "任务"   i, "描述信息", new Date()));
        }
    }

    /**
     * 查询所有任务
     *
     * @return
     */
    @RequestMapping(value = "jobs", method = RequestMethod.GET)
    @ResponseBody
    public Result<List<Job>> getAll() {
        System.out.println("RestfulController-------------------------getAll");
        return Result.success(jobList);
    }

    /**
     * 查询单个任务
     *
     * @param id
     * @return
     */
    @RequestMapping(value = "job/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Result<Job> getOne(@PathVariable("id") int id) {
        System.out.println("RestfulController-------------------------getOne");
        for (Job job : jobList) {
            if (job.getId() == id) {
                return Result.success(job);
            }
        }
        return Result.warn("无数据");
    }

    /**
     * 任务操作页面跳转
     *
     * @return
     */
    @RequestMapping("hello")
    public String hello() {
        return "jsp/restful";
    }

    /**
     * 任务添加
     *
     * @param job
     * @return
     */
    @RequestMapping(value = "job", method = RequestMethod.POST)
    @ResponseBody
    public Result<Boolean> add(Job job) {
        System.out.println("RestfulController-------------------------add");
        jobList.add(job);
        return Result.success(true);
    }

    /**
     * 任务更新
     *
     * @param id
     * @param jobData
     * @return
     */
    @RequestMapping(value = "job/{id}", method = RequestMethod.PUT)
    @ResponseBody
    public Result<Boolean> update(@PathVariable("id") int id, Job jobData) {
        System.out.println(id);
        for (Job job : jobList) {
            if (job.getId() == id) {
                job.setName(jobData.getName());
                job.setDescription(jobData.getDescription());
                return Result.success(true);
            }
        }
        return Result.warn(404, "更新的ID不存在");
    }

    /**
     * 任务删除
     *
     * @param id
     * @return
     */
    @RequestMapping(value = "job/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public Result<Boolean> Delete(@PathVariable("id") int id) {
        System.out.println(id);
        for (Job job : jobList) {
            if (job.getId() == id) {
                jobList.remove(job);
                return Result.success(true);
            }
        }
        return Result.warn(500, "要删除的ID不存在");
    } 
}
学新通

5.3、修改前端页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>restful</title>
    <script type="text/javascript" src="/js/jquery-1.11.1.js"></script>
    <script type="text/javascript">
        $(function () {
            //查询所有GET
            $("#btnGetAll").click(function () {
                $.ajax({
                    type: "GET",
                    url: "jobs",
                    data: "",
                    dataType: "json",
                    success: function (result) {
                        if(result.code!=200){
                            alert(result.message);
                        }
                        let data=result.data;
                        let html = "";
                        $.each(data, function (i, d) {
                            html  = "id:"   d.id   ",name:"   d.name   ",description:"   d.description   "<br/>";
                        })
                        $("#showResult").html(html);
                    }
                })
            });
            //查询单个GET
            $("#btnGetOne").click(function () {
                $.ajax({
                    type: "GET",
                    url: "job/"   $("input[name='id']").val(),
                    data: "",
                    dataType: "json",
                    success: function (result) {
                        if(result.code!=200){
                            alert(result.message);
                        }
                        let data=result.data;
                        $("#showResult").html("id:"   data.id   ",name:"   data.name   ",description:"   data.description);
                    }
                })
            });

            //添加
            $("#btnPost").click(function () {
                $.ajax({
                    type: "POST",
                    url: "job",
                    data: $("#myForm").serialize(),
                    dataType: "json",
                    success: function (data) {
                        if(data.code!=200){
                            alert(data.message);
                        }
                        $("#showResult").html(data.data);
                    }
                })
            });

            //更新
            $("#btnPut").click(function () {
                $.ajax({
                    type: "POST",
                    url: "job/"   $("input[name='id']").val(),
                    data: $("#myForm").serialize()   "&_method=PUT",
                    dataType: "json",
                    success: function (data) {
                        if(data.code!=200){
                            alert(data.message);
                        }
                        $("#showResult").html(data.data);
                    }
                })
            });

            //删除
            $("#btnDel").click(function () {
                $.ajax({
                    type: "POST",
                    url: "job/"   $("input[name='id']").val(),
                    data: "_method=DELETE",
                    dataType: "json",
                    success: function (data) {
                        if(data.code!=200){
                            alert(data.message);
                        }
                        $("#showResult").html(data.data);
                    }
                })
            });

        });
    </script>
</head>
<body>
<form id="myForm" action="" method="post">
    任务ID:<input type="text" name="id"/>
    任务名称:<input type="text" name="name"/>
    任务描述:<input type="text" name="description"/>
    <br/>
    <hr/>
    <button type="button" id="btnGetAll">查询所有GET</button>
    <button type="button" id="btnGetOne">查询单个GET</button>
    <button type="button" id="btnPost">添加POST</button>
    <button type="button" id="btnPut">更新PUT</button>
    <button type="button" id="btnDel">删除DELETE</button>
</form>
<p id="showResult">

</p>
</body>
</html>
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbicee
系列文章
更多 icon
同类精品
更多 icon
继续加载