天天看點

【SpringMVC從入門到精通】05-RESTfulRESTful

筆記來源:【尚矽谷】SpringMVC教程丨一套快速上手spring mvc

文章目錄

  • RESTful
    • 1、RESTful 簡介
      • 1.1、資源
      • 1.2、資源的表述
      • 1.3、狀态轉移
    • 2、RESTful 實作
    • 3、使用 RESTful 模拟操作使用者資源
      • 3.1、使用者的查詢、添加
      • 3.2、使用者的修改、删除
    • 4、CharacterEncodingFilter 和 HiddenHttpMethodFilter 的配置順序
    • 總結

RESTful

1、RESTful 簡介

REST

Representational State Transfer

,表現層資源狀态轉移

  • 表現層/表示層:前端的視圖頁面到後端的控制層即為“表現層”
  • 資源:Web 工程部署到伺服器上後,目前 Web 工程中的内容在伺服器上都叫“資源”(萬物皆資源)
  • 狀态:資源的表現形式,例如,HTML/JSP 頁面、CSS/JS 檔案、圖檔/音頻/視訊等皆為資源的“狀态”
  • 轉移:浏覽器發送請求到伺服器,服務端就将請求的資源“轉移”到用戶端

RESTful

:基于

REST

建構的 API 就是

RESTful

風格

1.1、資源

  • 資源是一種看待伺服器的方式,即将伺服器看作是由很多離散的資源組成。每個資源是伺服器上一個可命名的抽象概念
  • 因為資源是一個抽象的概念,是以它不僅僅能代表伺服器檔案系統中的一個檔案、資料庫中的一張表等等具體的東西,也可以将資源設計的要多抽象有多抽象,隻要想象力允許而且用戶端應用開發者能夠了解
  • 與面向對象設計類似,資源是以名詞為核心來組織的,首先關注的是“名詞”。一個資源可以由一個或多個

    URI

    來辨別。

    URI

    既是資源的名稱,也是資源在 Web 上的位址。對某個資源感興趣的用戶端應用,可以通過資源的

    URI

    與其進行互動

URI

Uniform Resource Identifier

):統一資源标志符

URL

Uniform Resource Locator

):統一資源定位符

URI

是一個抽象的、高層次的概念,而

URL

是具體的方式。簡單來說,

URL

是一種

URI

1.2、資源的表述

  • 資源的表述是一段對于資源在某個特定時刻的狀态的描述。可以在用戶端-伺服器端之間轉移(交換)
  • 資源的表述可以有多種格式,例如

    HTML/XML/JSON/純文字/圖檔/視訊/音頻

    等等
  • 資源的表述格式可以通過協商機制來确定。請求-響應方向的表述通常使用不同的格式

1.3、狀态轉移

  • 狀态轉移說的是:在用戶端和伺服器端之間轉移(

    transfer

    )代表資源狀态的表述
  • 通過轉移和操作資源的表述,來間接實作操作資源的目的

2、RESTful 實作

RESTful

的實作,具體說就是:HTTP 協定裡面,四個表示操作方式的動詞

GET

POST

PUT

DELETE

它們分别對應四種基本操作:

  • GET

    用來擷取資源
  • POST

    用來建立資源
  • PUT

    用來更新資源
  • DELETE

    用來删除資源

REST

風格

URL

位址不使用問号鍵值對方式攜帶請求參數,而是:

提倡使用統一的風格設計,從前到後各個單詞使用斜杠分開,将要發送給伺服器的資料作為

URL

位址的一部分,以保證整體風格的一緻性

以往,我們通路資源的方式五花八問。例如,
  • 擷取使用者資訊通過

    getUserById

    /

    selectUserById

    /

    findUserById

    /等
  • 删除使用者資訊通過

    deleteUserById

    /

    removeUserById

  • 更新使用者資訊通過

    updateUser

    /

    modifyUser

    /

    saveUser

  • 新增使用者資訊通過

    addUser

    /

    createUser

    /

    insertUser

上述操作的資源都是使用者資訊。按照

RESTful

思想,既然操作的資源一樣,那麼請求路徑就應該一樣

用一張表格來對比傳統方式和

REST

風格對資源操作的差別

操作 傳統方式 REST 風格
查詢

getUserById?id=1

user/1

–>

get

請求
儲存

saveUser

user

–>

post

請求
删除

deleteUserById?id=1

user/1

–>

delete

請求
更新

updateUser

user

–>

put

請求

3、使用 RESTful 模拟操作使用者資源

需求分析:使用 RESTful 模拟使用者資源的增删改查,通過路徑中的占位符傳遞請求參數,通過不同的請求方式對應資源的不同操作

RESTful 路徑 請求方式 操作

/user

GET

查詢所有使用者資訊

/user/1

GET

根據使用者ID查詢使用者資訊

/user

POST

添加使用者資訊

/user/1

DELETE

根據使用者ID删除使用者資訊

/user

PUT

修改使用者資訊

3.1、使用者的查詢、添加

背景代碼實作

RESTfulController.java

@Controller
@RequestMapping("restfulcontroller")
public class RESTfulController {
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getAllUser() {
        System.out.println("查詢所有使用者資訊");
        return "success";
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public String getUserById(@PathVariable("id") String id) {
        System.out.println("根據使用者ID查詢使用者資訊:" + id);
        return "success";
    }

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String insertUser(User user) { // User 沿用之前的對象
        System.out.println("添加使用者資訊:" + user);
        return "success";
    }
    
    /** 注:由于 PUT、DELETE 請求比較特殊,後面再做補充 */
}
           

前台測試代碼

restful.html

<a th:href="@{/restfulcontroller/user}">查詢所有使用者資訊</a><br/>
<a th:href="@{/restfulcontroller/user/1}">根據使用者ID查詢使用者資訊</a><br/>
<form th:action="@{/restfulcontroller/user}" method="post">
    使用者名:<input type="text" name="username"><br/>
    密碼:<input type="password" name="password"><br/>
    性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
    年齡:<input type="number" name="age"><br/>
    郵箱:<input type="text" name="email"><br/>
    <input type="submit" value="添加使用者">
</form>
           

為了能夠通路到

restful.html

前台頁面資源,可以通過在控制器中定義一個控制器方法來傳回其視圖,也可以通過在04-SpringMVC 視圖中提到的

view-controller

視圖控制器代替。因為這裡隻是為了實作頁面跳轉,沒有其他請求過程的處理,是以可以通過在 SpringMVC 配置檔案中使用

<view-controller>

标簽進行設定

測試結果

【SpringMVC從入門到精通】05-RESTfulRESTful

背景日志資訊

查詢所有使用者資訊
根據使用者ID查詢使用者資訊:1
添加使用者資訊:User{username='admin', password='11111', gender='male', age='18', email='[email protected]'}
           

3.2、使用者的修改、删除

在[email protected] 注解中,我們提到過

form

表單預設隻支援

GET

POST

請求,如果直接通過

method

屬性指定為

PUT

DELETE

,會預設以

GET

請求方式處理

而想要實作

PUT

DELETE

請求,需要在

web.xml

中配置 SpringMVC 提供的

HiddenHttpMethodFilter

過濾器,并在前台頁面使用隐藏域來設定

PUT

DELETE

類型的請求方式

當然,也可以使用

AJAX

發送

PUT

DELETE

請求,但是需要注意

PUT

DELETE

僅部分浏覽器支援

為了更清楚地了解

HiddenHttpMethodFilter

過濾器到底幹了什麼,這裡對其源碼進行剖析

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),HttpMethod.DELETE.name(),HttpMethod.PATCH.name()));
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = DEFAULT_METHOD_PARAM;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        // 要求原請求方式為 POST 請求
        if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
            // methodParam值:_method
            // paramValue值:_method屬性值
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                // _method屬性值轉為全大寫形式,put==>PUT,delete==>DELETE
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // 判斷_method屬性值是否為{"PUT","DELETE","PATCH"}中的一個
                if (ALLOWED_METHODS.contains(method)) {
                    // “偷梁換柱”,包裝為一個新的請求對象
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }
        filterChain.doFilter(requestToUse, response);
    }
    
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;
        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }
        @Override
        public String getMethod() {
            return this.method;
        }
    }
}
           

“源碼在手,天下我有”。接下來,将理論付諸實踐

配置檔案

web.xml

配置

HiddenHttpMethodFilter

過濾器

<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>
           

背景代碼實作

@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String updateUser(User user) {
    System.out.println("修改使用者資訊:" + user);
    return "success";
}
// 這裡的寫法格式沒有問題,但業務邏輯其實大有問題,下面再詳細說
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser(User user) {
    System.out.println("删除使用者資訊:" + user);
    return "success";
}
           

前台測試代碼

在添加使用者的表單基礎上,添加隐藏域

<input type="hidden" name="_method" value="put">

<form th:action="@{/restfulcontroller/user}" method="post">
    <input type="hidden" name="_method" value="put">
    使用者名:<input type="text" name="username"><br/>
    密碼:<input type="password" name="password"><br/>
    性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
    年齡:<input type="number" name="age"><br/>
    郵箱:<input type="text" name="email"><br/>
    <input type="submit" value="修改使用者">
</form>
<hr/>
<!-- 這裡的寫法格式沒有問題,但業務邏輯其實大有問題,下面再詳細說 -->
<form th:action="@{/restfulcontroller/user}" method="post">
    <input type="hidden" name="_method" value="delete">
    使用者名:<input type="text" name="username"><br/>
    密碼:<input type="password" name="password"><br/>
    性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
    年齡:<input type="number" name="age"><br/>
    郵箱:<input type="text" name="email"><br/>
    <input type="submit" value="删除使用者">
</form>
           

測試結果

修改使用者

【SpringMVC從入門到精通】05-RESTfulRESTful

删除使用者

【SpringMVC從入門到精通】05-RESTfulRESTful

背景日志資訊

修改使用者資訊:User{username='user', password='11111', gender='female', age='1', email='[email protected]'}
删除使用者資訊:User{username='user', password='11111', gender='female', age='1', email='[email protected]'}
           

到這裡,應該需要指明的是我們在設計

RESTful

實作時,對删除使用者資訊的要求下面這樣的

RESTful 路徑 請求方式 操作

/user/1

DELETE

根據使用者ID删除使用者資訊

換句話說,這裡應該通過超連結而非表單形式,即通過使用者ID來對使用者資訊進行删除操作。而一般情況下,我們是通過行編輯删除某一行資料,或是通過選中表單的資料來進行批量删除,這些功能在詳細案例時會詳細介紹

4、CharacterEncodingFilter 和 HiddenHttpMethodFilter 的配置順序

目前,我們在

web.xml

配置檔案中對

CharacterEncodingFilter

HiddenHttpMethodFilter

的配置順序如下

<!--處理編碼-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 略 -->
<!--配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 請求轉為 DELETE 或 POST 請求 -->
<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>
           

如果,将兩者順序颠倒互換,即

<!--配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 請求轉為 DELETE 或 POST 請求 -->
<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>
<!-- 略 -->
<!--處理編碼-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
           

看看順序改變之後,對請求的編碼會有什麼影響

【SpringMVC從入門到精通】05-RESTfulRESTful

背景日志資訊

修改使用者資訊:User{username='å¼ ä¸‰', password='123456', gender='male', age='18', email='[email protected]'}
           

可以發現,中文的“張三”亂碼了,這是為什麼呢?

在02-SpringMVC 擷取請求參數一節中的7、處理亂碼問題中,我們嘗試在擷取請求參數之前,通過

request.setCharacterEncoding("UTF-8");

來設定請求編碼格式,但是沒有生效。分析的原因是“請求參數擷取在前,設定編碼格式在後”導緻的。

我們也提出了 2 種解決方案:

  • 1、擷取請求參數之後,手動解碼編碼。但是這種顯然不合理,是以直接 pass
  • 2、擷取請求參數之前“做手腳”。這種方式就是 SpringMVC 中提供的

    CharacterEncodingFilter

    過濾器,來對請求編碼做統一處理
現在的問題就是:在

CharacterEncodingFilter

之前配置了

HiddenHttpMethodFilter

導緻了失效

是以我們需要搞清楚,為什麼

CharacterEncodingFilter

的配置順序會影響到編碼的效果?或者說為什麼

HiddenHttpMethodFilter

會使之失效?

通過上面對

HiddenHttpMethodFilter

源碼的剖析,它會獲得

_method

這個請求參數,這就導緻執行到

CharacterEncodingFilter

過濾器時,已經是擷取請求參數之後了,是以會導緻上述中文亂碼問題

是以,我們必須要将

CharacterEncodingFilter

過濾器盡量配置在其他過濾器之前。這樣就能保證在任何過濾器擷取請求之前,獲得的失已經處理過編碼格式的請求參數了

我們再将

CharacterEncodingFilter

HiddenHttpMethodFilter

的配置順序還原至之前的狀态,即

CharacterEncodingFilter

在前而

HiddenHttpMethodFilter

在後的情況,進行測試再檢視背景日志資訊

修改使用者資訊:User{username='張三', password='', gender='male', age='18', email='[email protected]'}
           

這時,當請求參數中包含中文時,就不會出現亂碼的情況了

總結

本節重點掌握内容

  • 明确

    REST

    RESTful

    的關系,明确表現層、資源、狀态、轉移這幾個概念的含義
    • REST

      ,表現層資源狀态轉移
    • RESTful

      ,基于

      REST

      建構的 API 就是

      RESTful

      風格
  • 明确

    RESTful

    的實作,是通過不同的請求方式來對應資源的不同操作,通過路徑中的占位符傳遞請求參數
  • 熟練掌握如何通過

    RESTful

    進行資源的增删改查操作,以及如何處理

    PUT

    DELETE

    這兩種特殊的請求方式
  • 明确

    CharacterEncodingFilter

    HiddenHttpMethodFilter

    的配置順序,明白兩個過濾器的源碼處理邏輯

附上導圖,僅供參考

【SpringMVC從入門到精通】05-RESTfulRESTful

繼續閱讀