筆記來源:【尚矽谷】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
既是資源的名稱,也是資源在 Web 上的位址。對某個資源感興趣的用戶端應用,可以通過資源的URI
與其進行互動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 風格 |
---|---|---|
查詢 | | –> 請求 |
儲存 | | –> 請求 |
删除 | | –> 請求 |
更新 | | –> 請求 |
3、使用 RESTful 模拟操作使用者資源
需求分析:使用 RESTful 模拟使用者資源的增删改查,通過路徑中的占位符傳遞請求參數,通過不同的請求方式對應資源的不同操作
RESTful 路徑 | 請求方式 | 操作 |
---|---|---|
| | 查詢所有使用者資訊 |
| | 根據使用者ID查詢使用者資訊 |
| | 添加使用者資訊 |
| | 根據使用者ID删除使用者資訊 |
| | 修改使用者資訊 |
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>
标簽進行設定
測試結果
背景日志資訊
查詢所有使用者資訊
根據使用者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
中配置 SpringMVC 提供的
web.xml
過濾器,并在前台頁面使用隐藏域來設定
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>
測試結果
修改使用者
删除使用者
背景日志資訊
修改使用者資訊: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 路徑 | 請求方式 | 操作 |
---|---|---|
| | 根據使用者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>
看看順序改變之後,對請求的編碼會有什麼影響
背景日志資訊
修改使用者資訊: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
建構的 API 就是REST
風格RESTful
-
- 明确
的實作,是通過不同的請求方式來對應資源的不同操作,通過路徑中的占位符傳遞請求參數RESTful
- 熟練掌握如何通過
進行資源的增删改查操作,以及如何處理RESTful
和PUT
這兩種特殊的請求方式DELETE
- 明确
和CharacterEncodingFilter
的配置順序,明白兩個過濾器的源碼處理邏輯HiddenHttpMethodFilter
附上導圖,僅供參考