• 笔记67 Spring Boot快速入门(七)


    SpringBoot+RESTful+JSON

    一、RESTful架构

      REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

      REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

    1.统一资源接口

      RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GET,PUT和POST,并遵循这些方法的语义。

      如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性,例如GET和HEAD请求都是安全的, 无论请求多少次,都不会改变服务器状态。而GET、HEAD、PUT和DELETE请求都是幂等的,无论对资源操作多少次, 结果总是一样的,后面的请求并不会产生比第一次更多的影响(幂等)

      下面列出了GET,DELETE,PUT和POST的典型用法:

    GET DELETE PUT POST
    • 安全且幂等
    • 获取表示
    • 变更时获取表示(缓存)
    • 200(OK) - 表示已在响应中发出
    • 204(无内容) - 资源有空表示
    • 301(Moved Permanently) - 资源的URI已被更新
    • 303(See Other) - 其他(如,负载均衡)
    • 304(not modified)- 资源未更改(缓存)
    • 400 (bad request)- 指代坏请求(如,参数错误)
    • 404 (not found)- 资源不存在
    • 406 (not acceptable)- 服务端不支持所需表示
    • 500 (internal server error)- 通用错误响应
    • 503 (Service Unavailable)- 服务端当前无法处理请求
    • 不安全且不幂等
    • 使用服务端管理的(自动产生)的实例号创建资源
    • 创建子资源
    • 部分更新资源
    • 如果没有被修改,则不过更新资源(乐观锁)
    • 200(OK)- 如果现有资源已被更改
    • 201(created)- 如果新资源被创建
    • 202(accepted)- 已接受处理请求但尚未完成(异步处理)
    • 301(Moved Permanently)- 资源的URI被更新
    • 303(See Other)- 其他(如,负载均衡)
    • 400(bad request)- 指代坏请求
    • 404 (not found)- 资源不存在
    • 406 (not acceptable)- 服务端不支持所需表示
    • 409 (conflict)- 通用冲突
    • 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
    • 415 (unsupported media type)- 接受到的表示不受支持
    • 500 (internal server error)- 通用错误响应
    • 503 (Service Unavailable)- 服务当前无法处理请求
    • 不安全但幂等
    • 用客户端管理的实例号创建一个资源
    • 通过替换的方式更新资源
    • 如果未被修改,则更新资源(乐观锁)
    • 200 (OK)- 如果已存在资源被更改
    • 201 (created)- 如果新资源被创建
    • 301(Moved Permanently)- 资源的URI已更改
    • 303 (See Other)- 其他(如,负载均衡)
    • 400 (bad request)- 指代坏请求
    • 404 (not found)- 资源不存在
    • 406 (not acceptable)- 服务端不支持所需表示
    • 409 (conflict)- 通用冲突
    • 412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
    • 415 (unsupported media type)- 接受到的表示不受支持
    • 500 (internal server error)- 通用错误响应
    • 503 (Service Unavailable)- 服务当前无法处理请求
    • 不安全但幂等
    • 删除资源
    • 200 (OK)- 资源已被删除
    • 301 (Moved Permanently)- 资源的URI已更改
    • 303 (See Other)- 其他,如负载均衡
    • 400 (bad request)- 指代坏请求
    • 404 (not found)- 资源不存在
    • 409 (conflict)- 通用冲突
    • 500 (internal server error)- 通用错误响应
    • 503 (Service Unavailable)- 服务端当前无法处理请求

    小结:

    <1>POST和PUT用于创建资源时有什么区别?

      POST和PUT在创建资源的区别在于,所创建的资源的名称(URI)是否由客户端决定。 例如为我的博文增加一个java的分类,生成的路径就是分类名/categories/java,那么就可以采用PUT方法。

    <2>PATCH这种不是HTTP标准方法。

    <3>统一资源接口对URI有什么指导意义?

    统一资源接口要求使用标准的HTTP方法对资源进行操作,所以URI只应该来表示资源的名称,而不应该包括资源的操作。

    通俗来说,URI不应该使用动作来描述。例如,下面是一些不符合统一接口要求的URI:

            • GET /getUser/1
            • POST /createUser
            • PUT /updateUser/1
            • DELETE /deleteUser/1

    二、RESTful风格

      简而言之,什么是REST? 就是看Url就知道要什么,看http method就知道干什么。

      在Web开发的过程中,method常用的值是get和post。可事实上,method值还可以是put和delete等等其他值。
    既然method值如此丰富,那么就可以考虑使用同一个url,但是约定不同的method来实施不同的业务,这就是Restful的基本考虑。
    CRUD是最常见的操作,在使用Restful 风格之前,通常的增加做法是这样的:
    /addCategory?name=xxx

    可是使用了Restuful风格之后,增加就变成了:/category

    CRUD如下表所示,URL就都使用一样的 "/category",区别只是在于method不同,服务器根据method的不同来判断浏览器期望做的业务行为。

    1.CategoryController.java

      CRUD的RequestMapping都修改为了/category,以前用的注解叫做@RequestMapper,现在分别叫做 GetMapper, PutMapper, PostMapper 和 DeleteMapper 用于表示接受对应的Method。

     1 package com.example.springbootrestfuldemo.controller;
     2 
     3 
     4 import com.example.springbootrestfuldemo.dao.CategoryDAO;
     5 import com.example.springbootrestfuldemo.pojo.Category;
     6 import com.sun.org.apache.xpath.internal.operations.Mod;
     7 import org.springframework.beans.factory.annotation.Autowired;
     8 import org.springframework.data.domain.Page;
     9 import org.springframework.data.domain.PageRequest;
    10 import org.springframework.data.domain.Pageable;
    11 import org.springframework.data.domain.Sort;
    12 import org.springframework.stereotype.Controller;
    13 import org.springframework.ui.Model;
    14 import org.springframework.web.bind.annotation.*;
    15 
    16 import java.util.List;
    17 
    18 @Controller
    19 public class CategoryController {
    20     @Autowired
    21     CategoryDAO categoryDAO;
    22 
    23     //restful风格
    24 
    25     //1.返回5条记录
    26     @GetMapping("/category")
    27     public String listCategory(Model model,@RequestParam(value = "start", defaultValue = "0") int start, @RequestParam(value = "size", defaultValue = "5") int size) throws Exception {
    28         start = start<0?0:start;
    29         Sort sort = new Sort(Sort.Direction.DESC, "id");
    30         Pageable pageable = new PageRequest(start, size, sort);
    31         Page<Category> page =categoryDAO.findAll(pageable);
    32         model.addAttribute("page",page);
    33         return "listCategories";
    34     }
    35     //2.保存一条记录
    36     @PutMapping("/category")
    37     public String addCategories(Category category) throws Exception {
    38         System.out.println("保存一条记录");
    39         categoryDAO.save(category);
    40         return "redirect:/category";
    41     }
    42     //3.删除一条记录
    43     @DeleteMapping("/category/{id}")
    44     public String deleteCategory(Category category){
    45         System.out.println("删除一条记录!");
    46         categoryDAO.delete(category);
    47         return "redirect:/category";
    48     }
    49 
    50     //4.更新一条记录
    51     @PostMapping("/category/{id}")
    52     public String updateCategory(Category category){
    53         System.out.println("更新一条记录!");
    54         categoryDAO.save(category);
    55         return "redirect:/category";
    56     }
    57     //6.跳转到编辑页
    58     @GetMapping("/category/{id}")
    59     public String addCategory(@PathVariable("id") int id, Model model) throws Exception{
    60         System.out.println("编辑视图");
    61         Category category=categoryDAO.getOne(id);
    62         model.addAttribute("c",category);
    63         return "editCategory";
    64     }
    65     
    66 }

    2.listCategories.html

     1 <!DOCTYPE html>
     2 <html lang="en" xmlns:th="http://www.thymeleaf.org">
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     5     <title>Title</title>
     6 </head>
     7 <body>
     8 <div class="showing">
     9     <h2>springboot+jpa+thymeleaf+Restful</h2>
    10     <div style="500px;margin:20px auto;text-align: center">
    11         <table align="center" border="1" cellspacing="0">
    12             <thead>
    13             <tr>
    14                 <th>id</th>
    15                 <th>name</th>
    16                 <td>编辑</td>
    17                 <td>删除</td>
    18             </tr>
    19             </thead>
    20             <tbody>
    21             <tr th:each="c: ${page}">
    22                 <td align="center" th:text="${c.id}"></td>
    23                 <td align="center" th:text="${c.name}"></td>
    24                 <td align="center" ><a th:href="@{/category/{id}(id=${c.id})}">编辑</a></td>
    25                 <td>
    26                     <form  th:action="@{/category/{id}(id=${c.id})}" action="/category" method="post">
    27                         <input type="hidden" name="_method" value="DELETE"/>
    28                         <button type="submit">删除</button>
    29                     </form>
    30                 </td>
    31             </tr>
    32             </tbody>
    33         </table>
    34         <br />
    35         <div>
    36             <a th:href="@{/category(start=0)}">[首  页]</a>
    37             <a th:href="@{/category(start=${page.number -1})}">[上一页]</a>
    38             <a th:if="${page.number!=page.totalPages -1}" th:href="@{/category(start=${page.number +1})}">[下一页]</a>
    39             <a th:href="@{/category(start=${page.totalPages -1})}">[末  页]</a>
    40         </div>
    41         <form action="/category" method="post">
    42             <!--修改提交方式为PUT-->
    43             <input type="hidden" name="_method" value="PUT"/>
    44             name:<input name="name"/><br/>
    45             <button type="submit">提交</button>
    46         </form>
    47 
    48 
    49     </div>
    50 </div>
    51 </body>
    52 </html>

    <1>增加

    A:action修改为"/category"
    B:增加如下<input type="hidden" name="_method" value="PUT">,虽然这个form的method是post, 但是spingboot看到这个_method的值是put后,会把其修改为put.

    1         <form action="/category" method="post">
    2             <!--修改提交方式为PUT-->
    3             <input type="hidden" name="_method" value="PUT"/>
    4             name:<input name="name"/><br/>
    5             <button type="submit">提交</button>
    6         </form>

    <2>删除

      原来的删除是通过超链接进行跳转,通过jQuery或者js可以将超链接的提交方式改变成成表单提交。然而我不会,所以用笨办法,<td>里面直接放一个form。

    1                 <td>
    2                     <form  th:action="@{/category/{id}(id=${c.id})}" action="/category" method="post">
    3                         <input type="hidden" name="_method" value="DELETE"/>
    4                         <button type="submit">删除</button>
    5                     </form>
    6                 </td>

    <3>修改

    注意thymeleaf构建url的方式

    1 <td align="center" ><a th:href="@{/category/{id}(id=${c.id})}">编辑</a></td>

    3.editCategory.html

    修改action中的地址

     1 <!DOCTYPE html>
     2 <html lang="en" xmlns:th="http://www.thymeleaf.org">
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     5     <title>Title</title>
     6 </head>
     7 <body>
     8 <div class="showing">
     9     <h2>springboot+jpa</h2>
    10 
    11     <div style="margin:0px auto; 500px">
    12 
    13         <form th:action="@{/category/{id}(id=${c.id})}"  method="post">
    14 
    15             name: <input name="name"  th:value="${c.name}" /> <br/>
    16 
    17             <input name="id" type="hidden" th:value="${c.id}" />
    18             <button type="submit">提交</button>
    19 
    20         </form>
    21     </div>
    22 </div>
    23 </body>
    24 </html>

    4.完整代码:https://github.com/lyj8330328/springboot-restful-demo

    三、分别是以json方式:提交,获取单个和获取多个

    提交:http://localhost:8080/submit.html

    获取单个:http://localhost:8080/getOne.html

    获取多个:http://localhost:8080/getMany.html

    基于Restful 风格的springboot进行修改。

    1.修改Category.java

    ①增加个toString() 方便,便于显示
    ②增加个注解:@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" }) ,否则会出错

     1 package com.example.springbootjpademo.pojo;
     2 
     3 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
     4 
     5 import javax.persistence.*;
     6 
     7 @Entity
     8 @Table(name = "category")
     9 @JsonIgnoreProperties({ "handler","hibernateLazyInitializer" })
    10 public class Category {
    11     @Id
    12     @GeneratedValue(strategy = GenerationType.IDENTITY)
    13     @Column(name = "id")
    14     private int id;
    15 
    16     @Column(name = "name")
    17     private String name;
    18     public int getId() {
    19         return id;
    20     }
    21     public void setId(int id) {
    22         this.id = id;
    23     }
    24     public String getName() {
    25         return name;
    26     }
    27     public void setName(String name) {
    28         this.name = name;
    29     }
    30 
    31     public String toString() {
    32         return "Category [id=" + id + ", name=" + name + "]";
    33     }
    34 }

    2.CategoryController.java

    控制器里提供3个方法,分别用来处理json 提交,json获取单个对象,json获取多个对象。

     1 package com.example.springbootjpademo.controller;
     2 
     3 import com.example.springbootjpademo.dao.CategoryDAO;
     4 import com.example.springbootjpademo.pojo.Category;
     5 import org.springframework.beans.factory.annotation.Autowired;
     6 import org.springframework.data.domain.Page;
     7 import org.springframework.data.domain.PageRequest;
     8 import org.springframework.data.domain.Pageable;
     9 import org.springframework.data.domain.Sort;
    10 import org.springframework.stereotype.Controller;
    11 import org.springframework.ui.Model;
    12 import org.springframework.web.bind.annotation.*;
    13 
    14 import java.util.List;
    15 //@Controller
    16 @RestController
    17 public class CategoryController {
    18     @Autowired
    19     CategoryDAO categoryDAO;
    20 
    21     @GetMapping("/category")
    22     public List<Category> listCategory(@RequestParam(value = "start", defaultValue = "0") int start,@RequestParam(value = "size", defaultValue = "5") int size) throws Exception {
    23         start = start<0?0:start;
    24         Sort sort = new Sort(Sort.Direction.DESC, "id");
    25         Pageable pageable = new PageRequest(start, size, sort);
    26         Page<Category> page =categoryDAO.findAll(pageable);
    27         return page.getContent();
    28     }
    29 
    30     @GetMapping("/category/{id}")
    31     public Category getCategory(@PathVariable("id") int id) throws Exception {
    32         Category c= categoryDAO.getOne(id);
    33         System.out.println(c);
    34         return c;
    35     }
    36     @PutMapping("/category")
    37     public void addCategories(@RequestBody Category category) throws Exception {
    38         Category category1=new Category();
    39         category1.setName(category.getName());
    40         categoryDAO.save(category1);
    41         System.out.println("springboot接受到浏览器以JSON格式提交的数据:"+category);
    42     }
    43 }

    3.submit.html

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     5     <title>用AJAX以JSON方式提交数据</title>
     6     <script type="text/javascript" src="js/jquery.min.js"></script>
     7 </head>
     8 <body>
     9 <form >
    10     id:<input type="text" id="id" value="123" /><br/>
    11     名称:<input type="text" id="name" value="category xxx"/><br/>
    12     <input type="button" value="提交" id="sender" />
    13 </form>
    14 <div id="messageDiv"></div>
    15 
    16 <script>
    17     $('#sender').click(function(){
    18         var id=document.getElementById('id').value;
    19         var name=document.getElementById('name').value;
    20         var category={"name":name,"id":id};
    21         var jsonData = JSON.stringify(category);
    22         var page="category";
    23 
    24         $.ajax({
    25             type:"put",
    26             url: page,
    27             data:jsonData,
    28             dataType:"json",
    29             contentType : "application/json;charset=UTF-8",
    30             success: function(result){
    31             }
    32         });
    33         alert("提交成功,请在springboot控制台查看服务端接收到的数据");
    34 
    35     });
    36 </script>
    37 </body>
    38 
    39 </html>

    4.getOne.html

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
     5 <title>用AJAX以JSON方式获取数据</title> 
     6 <script type="text/javascript" src="js/jquery.min.js"></script> 
     7 </head> 
     8 <body> 
     9     <input type="button" value="通过AJAX获取一个Hero对象---" id="sender">  
    10     
    11     <div id="messageDiv"></div> 
    12         
    13     <script> 
    14     $('#sender').click(function(){ 
    15         var url="category/12";
    16         $.get(
    17                 url,
    18                 function(data) {
    19                     console.log(data);
    20                      var json=data; 
    21                      var name =json.name; 
    22                      var id = json.id;
    23                      $("#messageDiv").html("分类id:"+ id + "<br>分类名称:" +name );
    24                        
    25          });  
    26     }); 
    27     </script> 
    28 </body>
    29 </html>

    5.getMany.html

     1 <!DOCTYPE html>
     2 <html>
     3 <head>
     4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
     5 <title>用AJAX以JSON方式获取数据</title> 
     6 <script type="text/javascript" src="js/jquery.min.js"></script> 
     7 </head> 
     8 <body> 
     9     <input type="button" value="通过AJAX获取多个分类对象" id="sender">  
    10     
    11     <div id="messageDiv"></div> 
    12         
    13     <script> 
    14     $('#sender').click(function(){ 
    15         var url="category?start=0&size=100"; 
    16         $.get(
    17                 url,
    18                 function(data) {
    19                     var categorys = data;
    20                      for(i in categorys){
    21                          var old = $("#messageDiv").html();
    22                          var category = categorys[i];
    23                          $("#messageDiv").html(old + "<br>"+category.id+"   -----   "+category.name);
    24                      }
    25          });  
    26     }); 
    27     </script> 
    28 </body> 
    29     
    30 </body>
    31 </html>

    6.直接调试,即采用AJAX的方式。也可采用Postman,结果如下:

    <1>请求http://localhost:8080/category,应返回5条记录,默认分页大小为5。注意请求的方式必须是GET,否则会报错。

    <2>请求http://localhost:8080/category/12,返回id=12的对象。

    <3>获取全部数据http://localhost:8080/category?start=0&size=100

    7.代码:https://github.com/lyj8330328/springboot-jpa-demo

    四、总结

    1.访问方式

    通过http://localhost:8080/submit.html这种形式访问视图,必须将视图文件放入src/main目录下的webapp文件夹中,这样才能访问到视图,放在resources下的templates中就会出错。

    2.注解

    <1>@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" })

    因为懒加载这个对象属性只是一个代理对象,如果json直接当作一个存在的属性去序列化就会出现错误。

    <2>@RestController

    @RestController is a stereotype annotation that combines @ResponseBody and @Controller.
    意思是:
    @RestController注解相当于@ResponseBody + @Controller合在一起的作用。

    1)如果只是使用@RestController注解Controller,则Controller中的方法无法返回视图,返回的内容就是Return 里的内容。

    2)如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
    3)如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

  • 相关阅读:
    android 休眠唤醒机制分析(三) — suspend
    android 休眠唤醒机制分析(一) — wake_lock
    开机音乐不发声的问题
    Linux的时钟管理
    Android4.2增加新键值
    _IO, _IOR, _IOW, _IOWR 宏的用法与解析
    Mifare 0简介
    Mifare 1卡的存储结构
    Maven 介绍
    DAL 层引用 System.Net.Http ,引发的一阵心慌
  • 原文地址:https://www.cnblogs.com/lyj-gyq/p/9314425.html
Copyright © 2020-2023  润新知