[Re] SpringMVC-1
概述
- SpringMVC 是 Spring 的 Web 模組。
- Spring 為展現層提供的基於 MVC 設計理念的優秀的 Web 框架,是目前最主流的 MVC 框架之一。
- Spring3.0 後全面超越 Struts2,成為最優秀的 MVC 框架。
- Spring MVC 通過一套 MVC 註解,讓 POJO(Plain Old Java Object) 成為處理請求的控制器,而無須實現任何介面。
- 支援 REST 風格的 URL 請求。
- 採用了鬆散耦合可插拔元件結構,比其他 MVC 框架更具擴充套件性和靈活性。
HelloWorld
步驟
- 建立 Web 工程
- 導包
- SpringMVC 是 Spring 的 Web 模組;所有模組的執行都是依賴核心模組[IOC 模組]
commons-logging-1.1.3.jar // 日誌 spring-aop-4.0.0.RELEASE.jar // 支援註解 // 核心 ↓ spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar
- Web 模組
spring-web-4.0.0.RELEASE.jar spring-webmvc-4.0.0.RELEASE.jar
- SpringMVC 是 Spring 的 Web 模組;所有模組的執行都是依賴核心模組[IOC 模組]
- 寫配置檔案
- web.xml
<!-- SpringMVC 思想:有一個前端控制器能攔截所有請求,並智慧派發 這個前端控制器是一個 Servlet,應該在 web.xml 中配置這個 Servlet 來攔截所有請求 --> <!-- The front controller of this Spring Web application, responsible for handling all application requests --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!-- contextConfigLocation 指定 SpringMVC 配置檔案的位置 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <!-- Servlet 啟動載入,原本是在第 1 次訪問建立物件。設定該子標籤後, Servlet 會在 Server 啟動時建立物件。值越小,優先順序越高,越先建立物件 --> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <!-- '/*' 和 '/' 都是攔截所有請求。 但是 '/*' 範圍更廣,會攔截到 *.jsp 頁面,一旦攔截,jsp 頁面就無法顯示。 '/' 也會攔截所有請求,但不會攔截 jsp。 --> <url-pattern>/</url-pattern> </servlet-mapping>
- springMVC.xml (建立 Spring Bean Configuration File,即 Web 層的 Spring 容器)
<!-- 掃描元件 --> <context:component-scan base-package="cn.edu.nuist"></context:component-scan> <!-- 配置一個檢視解析器,能幫我們拼接頁面地址 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean>
- web.xml
- 編寫測試程式碼
- index.jsp
<a href="hello">HelloWorld!</a>
- success.jsp
<body>SUCCESS!</body>
- MyFirstController
@Controller // 告訴 SpringMVC 這是一個處理器,可以處理請求 public class MyFirstController { /* * '/' 代表從當前專案下開始,處理當前專案下的 hello 請求 */ @RequestMapping("/hello") public String myFirstRequest() { System.out.println("請求收到了,正在處理中..."); /* * [檢視解析器] 會自動拼串: * <property name="prefix" value="/WEB-INF/pages/"> * <property name="suffix" value=".jsp"> * → /WEB-INF/pages/[方法返回值].jsp */ // return "/WEB-INF/pages/success.jsp"; return "success"; } }
- index.jsp
細節
執行流程
- 客戶端點選超連結會發送
http://localhost:8080/21_SpringMVC/hello
請求 - 來到 tomcat 伺服器,SpringMVC 的 [前端控制器] 收到所有請求
- 來看請求地址和 @RequestMapping 標註的哪個匹配,來找使用哪個類的哪個方法來處理請求
- [前端控制器] 找到了 {目標處理器類&目標處理方法},直接利用反射執行目標方法
- 方法執行完成以後會有一個返回值:SpringMVC 認為這個返回值就是要去的頁面地址
- 拿到方法返回值後,用 [檢視解析器] 拼串得到完整的頁面地址
- 拿到頁面地址後,[前端控制器] 幫我們轉發到指定頁面
@RequestMapping 簡述
告訴 SpringMVC 這個方法用來處理什麼請求。
@RequestMapping("/hello")
這個 '/' 可以省略,即使省略,也是預設從當前專案下開始。不過習慣加上。
配置檔案的預設位置
可以看出,如果不指定配置檔案位置,SpringMVC 預設會去找如下檔案:
所以,若不想指定配置檔案位置,就在 web 應用的 WEB-INF 目錄下,指定配置檔名為 <servlet-name>-servlet.xml
即可。
2.2.4 核心控制器的 url-pattern
- Tomcat Server 的 web.xml 中有一個 DefaultServlet,其 url-pattern 的配置是
/
- The default servlet for all web applications, that serves static resources.
- DefaultServlet 就負責找到並返回靜態資源
- [前端控制器] 的 url-pattern 也配置為了
/
,這就相當於把 DefaultServlet 禁用了。此時,若再去訪問一個靜態資源(除去 Jsp 和 Servlet 外,其餘都是靜態資源)時,就會讓 [前端控制器] 來處理該請求,而 [前端控制器] 就會去比較哪個方法的@RequestMapping
與請求對應,肯定找不到,所以最終返回 404。 - 為什麼 Jsp 能訪問?
- 伺服器的 web.xml 中還有一個 JspServlet,其 url-pattern 配置為
*.jsp
。The JSP page compiler and execution servlet, which is the mechanism used by Tomcat to support JSP pages. Traditionally, this servlet is mapped to the URL pattern "*.jsp".
- 因為我們專案自身 web.xml 中並沒有覆蓋伺服器的 JspServlet 的配置;所以,JspServlet 能正常處理 Jsp 請求。
- 伺服器的 web.xml 中還有一個 JspServlet,其 url-pattern 配置為
- 當把 [前端控制器] 的 url-pattern 配置為了
/*
,這時,連 Jsp 也訪問不到了。
- 我們把專案的 url-pattern 配置為
/
也是為了迎合後來的 REST 風格的 URL 地址。
@RequestMapping
Spring MVC 使用 @RequestMapping 註解為控制器指定可以處理哪些 URL 請求。
註解標註在類上
在控制器的類定義及方法定義處都可標註 @RequestMapping。
- 類定義處:提供初步的請求對映資訊。相對於 WEB 應用的根目錄
- 方法處:提供進一步的細分對映資訊。相對於類定義處的 URL。若類定義處未標註 @RequestMapping,則方法處標記的 URL 相對於 WEB 應用的根目錄
註解中的屬性
標準的 HTTP 請求報頭:
屬性之間是與的關係,聯合使用多個條件可讓請求對映更加精確化。
對映請求路徑
DispatcherServlet 截獲請求後,就通過將請求 URL 與控制器上 @RequestMapping 註解的 value 屬性值進行比對以此確定請求所對應的處理方法。
對映請求方法
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
若不是按規定的方式請求:405 - Request method 'GET' not supported
對映請求引數
params 規定請求攜帶的請求引數,支援簡單的表示式 (不按要求的直接 404),引數規則間用 ,
隔開。
params={"username"} 請求必須攜帶名為 username 的引數
params={"!username"} 請求不能攜帶名為 username 的引數
params={"username=admin"} 請求必須攜帶名為 username 且值為 admin 的引數
params={"username!=admin"} 請求必須攜帶名為 username 但值不能是 admin 的引數
舉例:{"param1=value1", "param2"}
請求必須包含名為 param1 和 param2 的兩個請求引數,且 param1 引數的值必須為 value1。
對映請求頭
headers 規定請求頭,使用方式和 params 類似 (不按要求的也是直接 404)。
Ant 風格的 URL
- Ant 風格資源地址支援 3 種匹配符:
?:匹配檔名中的一個字元(0個/多個都不行) *:匹配檔名中的任意字元或者一層路徑(0層/多層都不行) **:匹配多層路徑
- @RequestMapping 還支援 Ant 風格的 URL
/user/*/createUser: 匹配 /user/aaa/createUser、/user/bbb/createUser 等 URL /user/**/createUser: 匹配 /user/createUser、/user/aaa/bbb/createUser 等 URL /user/createUser??: 匹配 /user/createUseraa、/user/createUserbb 等 URL
@PathVariable
帶佔位符的 URL 是 Spring3.0 新增的功能,該功能在 SpringMVC 向 REST 目標挺進發展過程中具有里程碑的意義。
通過 @PathVariable 可以將 URL 中佔位符引數繫結到控制器處理方法的形參中:URL 中的 {xxx} 佔位符可以通過 @PathVariable("xxx") 繫結到操作方法的形參中。
REST
REST 風格
- 簡述
- 示例(URL 起名方式:
/資源名/資源識別符號
)
- 【總結】URL 定位資源,用 HTTP 動詞(GET,POST,DELETE,PUT)描述操作。
- 看 URL 就知道要什麼
- 看 HTTP Method 就知道幹什麼
- 看 HTTP StatusCode 就知道結果如何
HiddenHttpMethodFilter
瀏覽器 form 表單只支援 GET 與 POST 請求,而DELETE、PUT 等 method 並不支援,Spring3.0 添加了這個過濾器,可以將這些請求轉換為標準的 HTTP 方法,使得支援 GET、POST、PUT 與 DELETE 請求。
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response
, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
} else {
filterChain.doFilter(request, 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;
}
}
}
請求處理
- Spring MVC 通過分析處理方法的簽名,HTTP 請求資訊繫結到處理方法的相應形參中。
- Spring MVC 對控制器處理方法簽名的限制是很寬鬆的,幾乎可以按喜歡的任何方式對方法進行簽名。
- 必要時可以對方法及方法形參標註相應的註解(@PathVariable 、@RequestParam、@RequestHeader 等)
- Spring MVC 框架會將 HTTP 請求的資訊繫結到相應的方法形參中,並根據方法的返回值型別做出相應的後續處理
@RequestParam
- 在處理方法形參處使用 @RequestParam 可以把請求引數傳遞給請求方法
- 註解屬性
- value 引數名
- required 是否必須。預設為 true,表示請求引數中必須包含對應的引數;若不存在,將丟擲異常
- defaultValue 預設值,當沒有傳遞引數時使用該值
@RequestHeader
- 使用 @RequestHeader 繫結請求報頭的屬性值
- 請求頭包含了若干個屬性,伺服器可據此獲知客戶端的資訊,通過 @RequestHeader 即可將請求頭中的屬性值繫結到處理方法的形參中
- 註解屬性同 @RequestParam
@CookieValue
使用 @CookieValue 繫結請求中的 Cookie 值到處理方法的某個形參。
POJO 作為引數
使用 POJO 物件繫結請求引數值時,Spring MVC 會按請求引數名和 POJO 屬性名進行自動匹配,自動為該物件填充屬性值。支援級聯屬性。如:dept.deptId、dept.address.tel 等。
ServletAPI 作為引數
MVC 的 Handler 方法可以接受如下 ServletAPI 型別的引數(原始碼參考:AnnotationMethodHandlerAdapter)
- HttpServletRequest
- HttpServletResponse
- HttpSession
- java.security.Principal
- Locale
- InputStream
- OutputStream
- Reader
- Writer
亂碼問題
GET 亂碼解決
POST 亂碼解決
配置字元編碼過濾器。但要注意:當有多個 Filter 時,一般都會將字元編碼 Filter 置首!因為 CharacterEncodingFilter 必須要在第一次獲取請求引數之前設定才有意義。
假設現在配了倆過濾器:HiddenHttpMethodFilter 和 CharacterEncodingFilter。如果先配置了 HiddenHttpMethodFilter,而其 doFilter()
中呼叫的 request.getParameter(this.methodParam)
會使得字元編碼過濾器失效。所以,一般在 web.xml 中都會先配置字元編碼過濾器。
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
private boolean forceEncoding = false;
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceEncoding = forceEncoding;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
response, FilterChain filterChain) throws ServletException, IOException {
if (this.encoding != null && (this.forceEncoding
|| request.getCharacterEncoding() == null)) {
// 設定請求編碼
request.setCharacterEncoding(this.encoding);
if (this.forceEncoding) {
// 設定響應編碼
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
}