Java開發學習(二十七)----SpringMVC之Rest風格解析及快速開發

2022-08-28 18:00:27

一、REST簡介

  • REST(Representational State Transfer),表現形式狀態轉換,它是一種軟體架構風格

    當我們想表示一個網路資源的時候,可以使用兩種方式:

    • 傳統風格資源描述形式

      • http://localhost/user/getById?id=1 查詢id為1的使用者資訊

      • http://localhost/user/saveUser 儲存使用者資訊

    • REST風格描述形式

      • http://localhost/user/1

      • http://localhost/user

傳統方式一般是一個請求url對應一種操作,這樣做不僅麻煩,也不安全,因為會程式的人讀取了你的請求url地址,就大概知道該url實現的是一個什麼樣的操作。

檢視REST風格的描述,你會發現請求地址變的簡單了,並且光看請求URL並不是很能猜出來該URL的具體功能

所以REST的優點有:

  • 隱藏資源的存取行為,無法通過地址得知對資源是何種操作

  • 書寫簡化

但是我們的問題也隨之而來了,一個相同的url地址即可以是新增也可以是修改或者查詢,那麼到底我們該如何區分該請求到底是什麼操作呢?

  • 按照REST風格存取資源時使用行為動作區分對資源進行了何種操作

    • http://localhost/users 查詢全部使用者資訊 採用GET請求(查詢)

    • http://localhost/users/1 查詢指定使用者資訊 採用GET請求(查詢)

    • http://localhost/users 新增使用者資訊 採用POST請求(新增/儲存)

    • http://localhost/users 修改使用者資訊 採用PUT請求(修改/更新)

    • http://localhost/users/1 刪除使用者資訊 採用DELETE請求(刪除)

請求的方式比較多,但是比較常用的就4種,分別是GET,POST,PUT,DELETE

按照不同的請求方式代表不同的操作型別。

  • 傳送GET請求是用來做查詢

  • 傳送POST請求是用來做新增

  • 傳送PUT請求是用來做修改

  • 傳送DELETE請求是用來做刪除

注意:

  • 上述行為是約定方式,約定不是規範,可以打破,所以稱REST風格,而不是REST規範

    • REST提供了對應的架構方式,按照這種架構設計專案可以降低開發的複雜性,提高系統的可伸縮性

    • REST中規定GET/POST/PUT/DELETE針對的是查詢/新增/修改/刪除,但是我們如果非要用GET請求做刪除,這點在程式上執行是可以實現的

  • 描述模組的名稱通常使用複數,也就是加s的格式描述,表示此類資源,而非單個資源,例如:users、books、accounts......

清楚了什麼是REST風格後,我們後期會經常提到一個概念叫RESTful,那什麼又是RESTful呢?

  • 根據REST風格對資源進行存取稱為RESTful

後期我們在進行開發的過程中,大多是都是遵從REST風格來存取我們的後臺服務,所以可以說咱們以後都是基於RESTful來進行開發的。

二、RESTful入門案例

2.1 環境準備

  • 建立一個Web的Maven專案

  • pom.xml新增Spring依賴

    <?xml version="1.0" encoding="UTF-8"?>
    ​
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    ​
      <groupId>com.itheima</groupId>
      <artifactId>springmvc_06_rest</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
    ​
      <dependencies>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.1.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
        </dependency>
      </dependencies>
    ​
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.1</version>
            <configuration>
              <port>80</port>
              <path>/</path>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    ​
  • 建立對應的設定類

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
        protected Class<?>[] getRootConfigClasses() {
            return new Class[0];
        }
    ​
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    ​
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    ​
        //亂碼處理
        @Override
        protected Filter[] getServletFilters() {
            CharacterEncodingFilter filter = new CharacterEncodingFilter();
            filter.setEncoding("UTF-8");
            return new Filter[]{filter};
        }
    }
    ​
    @Configuration
    @ComponentScan("com.itheima.controller")
    //開啟json資料型別自動轉換
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    ​
    ​
  • 編寫模型類User和Book

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
    ​
    public class Book {
        private String name;
        private double price;
         //getter...setter...toString省略
    }
  • 編寫UserController和BookController

    @Controller
    public class UserController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(@RequestBody User user) {
            System.out.println("user save..."+user);
            return "{'module':'user save'}";
        }
    ​
        @RequestMapping("/delete")
        @ResponseBody
        public String delete(Integer id) {
            System.out.println("user delete..." + id);
            return "{'module':'user delete'}";
        }
    ​
        @RequestMapping("/update")
        @ResponseBody
        public String update(@RequestBody User user) {
            System.out.println("user update..." + user);
            return "{'module':'user update'}";
        }
    ​
        @RequestMapping("/getById")
        @ResponseBody
        public String getById(Integer id) {
            System.out.println("user getById..." + id);
            return "{'module':'user getById'}";
        }
    ​
        @RequestMapping("/findAll")
        @ResponseBody
        public String getAll() {
            System.out.println("user getAll...");
            return "{'module':'user getAll'}";
        }
    }
    ​
    ​
    @Controller
    public class BookController {
        
        @RequestMapping(value = "/books",method = RequestMethod.POST)
        @ResponseBody
        public String save(@RequestBody Book book){
            System.out.println("book save..." + book);
            return "{'module':'book save'}";
        }
    ​
        @RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE)
        @ResponseBody
        public String delete(@PathVariable Integer id){
            System.out.println("book delete..." + id);
            return "{'module':'book delete'}";
        }
    ​
        @RequestMapping(value = "/books",method = RequestMethod.PUT)
        @ResponseBody
        public String update(@RequestBody Book book){
            System.out.println("book update..." + book);
            return "{'module':'book update'}";
        }
    ​
        @RequestMapping(value = "/books/{id}",method = RequestMethod.GET)
        @ResponseBody
        public String getById(@PathVariable Integer id){
            System.out.println("book getById..." + id);
            return "{'module':'book getById'}";
        }
    ​
        @RequestMapping(value = "/books",method = RequestMethod.GET)
        @ResponseBody
        public String getAll(){
            System.out.println("book getAll...");
            return "{'module':'book getAll'}";
        }
        
    }

最終建立好的專案結構如下:

2.2 思路分析

需求:將增刪改查替換成RESTful的開發方式。

1.不同的請求有不同的路徑,現在要將其修改為統一的請求路徑

修改前: 新增: /save ,修改: /update,刪除 /delete...

修改後: 增刪改查: /users

2.根據GET查詢、POST新增、PUT修改、DELETE刪除對方法的請求方式進行限定

3.傳送請求的過程中如何設定請求引數?

2.3 修改成為RESTful風格

新增
@Controller
public class UserController {
    //設定當前請求方法為POST,表示REST風格中的新增操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save() {
        System.out.println("user save...");
        return "{'module':'user save'}";
    }
}
  • 將請求路徑更改為/users

    • 存取該方法使用 POST: http://localhost/users

  • 使用method屬性限定該方法的存取方式為POST

    • 如果傳送的不是POST請求,比如傳送GET請求,則會報錯

刪除
@Controller
public class UserController {
    //設定當前請求方法為DELETE,表示REST風格中的刪除操作
    @RequestMapping(value = "/users",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}
  • 將請求路徑更改為/users

    • 存取該方法使用 DELETE: http://localhost/users

存取成功,但是刪除方法沒有攜帶所要刪除資料的id,所以針對RESTful的開發,如何攜帶資料引數?

傳遞路徑引數

前端傳送請求的時候使用:http://localhost/users/1,路徑中的1就是我們想要傳遞的引數。

後端獲取引數,需要做如下修改:

  • 修改@RequestMapping的value屬性,將其中修改為/users/{id},目的是和路徑匹配

  • 在方法的形參前新增@PathVariable註解

@Controller
public class UserController {
    //設定當前請求方法為DELETE,表示REST風格中的刪除操作
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}

思考如下兩個問題:

(1)如果方法形參的名稱和路徑{}中的值不一致,該怎麼辦?

(2)如果有多個引數需要傳遞該如何編寫?

前端傳送請求的時候使用:http://localhost/users/1/tom,路徑中的1tom就是我們想要傳遞的兩個引數。

後端獲取引數,需要做如下修改:

@Controller
public class UserController {
    //設定當前請求方法為DELETE,表示REST風格中的刪除操作
    @RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id,@PathVariable String name) {
        System.out.println("user delete..." + id+","+name);
        return "{'module':'user delete'}";
    }
}
修改
@Controller
public class UserController {
    //設定當前請求方法為PUT,表示REST風格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user) {
        System.out.println("user update..." + user);
        return "{'module':'user update'}";
    }
}
  • 將請求路徑更改為/users

    • 存取該方法使用 PUT: http://localhost/users

  • 存取並攜帶引數:

根據ID查詢
@Controller
public class UserController {
    //設定當前請求方法為GET,表示REST風格中的查詢操作
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }
}

將請求路徑更改為/users

  • 存取該方法使用 GET: http://localhost/users/666

查詢所有
@Controller
public class UserController {
    //設定當前請求方法為GET,表示REST風格中的查詢操作
    @RequestMapping(value = "/users" ,method = RequestMethod.GET)
    @ResponseBody
    public String getAll() {
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}

將請求路徑更改為/users

  • 存取該方法使用 GET: http://localhost/users

小結

RESTful入門案例,我們需要學習的內容如下:

(1)設定Http請求動作(動詞)

@RequestMapping(value="",method = RequestMethod.POST|GET|PUT|DELETE)

(2)設定請求引數(路徑變數)

@RequestMapping(value="/users/{id}",method = RequestMethod.DELETE)

@ReponseBody

public String delete(@PathVariable Integer id){

}

知識點1:@PathVariable

名稱 @PathVariable
型別 形參註解
位置 SpringMVC控制器方法形參定義前面
作用 繫結路徑引數與處理器方法形參間的關係,要求路徑引數名與形參名一一對應

關於接收引數三個註解@RequestBody@RequestParam@PathVariable,這三個註解之間的區別和應用分別是什麼?

  • 區別

    • @RequestParam用於接收url地址傳參或表單傳參

    • @RequestBody用於接收json資料

    • @PathVariable用於接收路徑引數,使用{引數名稱}描述路徑引數

  • 應用

    • 後期開發中,傳送請求引數超過1個時,以json格式為主,@RequestBody應用較廣

    • 如果傳送非json格式資料,選用@RequestParam接收請求引數

    • 採用RESTful進行開發,當引數數量較少時,例如1個,可以採用@PathVariable接收請求路徑變數,通常用於傳遞id值

三、RESTful快速開發

做完了RESTful的開發,你會發現好麻煩,麻煩在哪?

  • 每個方法的@RequestMapping註解中都定義了存取路徑/books,重複性太高。
  • 每個方法的@RequestMapping註解中都要使用method屬性定義請求方式,重複性太高。
  • 每個方法響應json都需要加上@ResponseBody註解,重複性太高。

對於上面所提的這三個問題,具體該如何解決?

@RestController //@Controller + ReponseBody
@RequestMapping("/books")
public class BookController {
    
    //@RequestMapping(method = RequestMethod.POST)
    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }
​
    //@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }
​
    //@RequestMapping(method = RequestMethod.PUT)
    @PutMapping
    public String update(@RequestBody Book book){
        System.out.println("book update..." + book);
        return "{'module':'book update'}";
    }
​
    //@RequestMapping(value = "/{id}",method = RequestMethod.GET)
    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..." + id);
        return "{'module':'book getById'}";
    }
​
    //@RequestMapping(method = RequestMethod.GET)
    @GetMapping
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
    
}

對於剛才的問題,我們都有對應的解決方案:

每個方法的@RequestMapping註解中都定義了存取路徑/books,重複性太高。

將@RequestMapping提到類上面,用來定義所有方法共同的存取路徑。

每個方法的@RequestMapping註解中都要使用method屬性定義請求方式,重複性太高。

使用@GetMapping  @PostMapping  @PutMapping  @DeleteMapping代替

每個方法響應json都需要加上@ResponseBody註解,重複性太高。

1.將ResponseBody提到類上面,讓所有的方法都有@ResponseBody的功能
2.使用@RestController註解替換@Controller與@ResponseBody註解,簡化書寫

知識點1:@RestController

名稱 @RestController
型別 類註解
位置 基於SpringMVC的RESTful開發控制器類定義上方
作用 設定當前控制器類為RESTful風格, 等同於@Controller與@ResponseBody兩個註解組合功能

知識點2:@GetMapping @PostMapping @PutMapping @DeleteMapping

名稱 @GetMapping @PostMapping @PutMapping @DeleteMapping
型別 方法註解
位置 基於SpringMVC的RESTful開發控制器方法定義上方
作用 設定當前控制器方法請求存取路徑與請求動作,每種對應一個請求動作, 例如@GetMapping對應GET請求
相關屬性 value(預設):請求存取路徑