Spring MVC簡介
Spring MVC(Spring Web MVC)是 Spring Framework 提供的 Web 組件,它的實現基於 MVC 的設計模式:Controller(控制層)、Model(模型層)、View(視圖層),提供了前端路由映射、視圖解析等功能,讓 Java Web 開發變得更加簡單,也屬於 Java 開發中必須要掌握的熱門框架。
執行流程
Spring MVC 的執行流程如下:
- 客戶端發送請求至前端控制器(DispatcherServlet)
- 前端控制器根據請求路徑,進入對應的處理器
- 處理器調用相應的業務方法
- 處理器獲取到相應的業務數據
- 處理器把組裝好的數據交還給前端控制器
- 前端控制器將獲取的 ModelAndView 對象傳給視圖解析器(ViewResolver)
- 前端控制器獲取到解析好的頁面數據
- 前端控制器將解析好的頁面返回給客戶端
流程如下圖所示:
核心組件
Spring MVC 的核心組件如下列表所示:
- DispatcherServlet:核心處理器(也叫前端控制器),負責調度其他組件的執行,可降低不同組件之間的耦合性,是整個 Spring MVC 的核心模塊。
- Handler:處理器,完成具體業務邏輯,相當於 Servlet 或 Action。
- HandlerMapping:DispatcherServlet 是通過 HandlerMapping 將請求映射到不同的 Handler。
- HandlerInterceptor:處理器攔截器,是一個接口,如果我們需要做一些攔截處理,可以來實現這個接口。
- HandlerExecutionChain:處理器執行鏈,包括兩部分內容,即 Handler 和 HandlerInterceptor(系統會有一個默認的 HandlerInterceptor,如果需要額外攔截處理,可以添加攔截器設置)。
- HandlerAdapter:處理器適配器,Handler 執行業務方法之前,需要進行一系列的操作包括表單數據的驗證、數據類型的轉換、將表單數據封裝到 POJO 等,這一系列的操作,都是由 HandlerAdapter 來完成,DispatcherServlet 通過 HandlerAdapter 執行不同的 Handler。
- ModelAndView:裝載了模型數據和視圖信息,作爲 Handler 的處理結果,返回給 DispatcherServlet。
- ViewResolver:視圖解析器,DispatcherServlet 通過它將邏輯視圖解析成物理視圖,最終將渲染結果響應給客戶端。
自動類型轉換
自動類型轉換指的是,Spring MVC 可以將表單中的字段,自動映射到實體類的對應屬性上,請參考以下示例。
1.JSP 頁面代碼
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<form action="add">
名稱:<input type="input" name="name"><br>
年齡:<input type="input" name="age"><br>
<input type="submit" value=" 提交 ">
</form>
</body>
</html>
2. 編寫實體類
public class PersonDTO {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3. 編寫控制器
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonController {
@RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")
public String add(PersonVO person) {
return person.getName() + ":" + person.getAge();
}
}
中文亂碼處理
業務的操作過程中可能會出現中文亂碼的情況,以下是處理中文亂碼的解決方案。
第一步,在 web.xml 添加編碼過濾器,配置如下:
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第二步,設置 RequestMapping 的 produces 屬性,指定返回值類型和編碼,如下所示:
@RequestMapping(value = "/add", produces = "text/plain;charset=utf-8")
攔截器
在 Spring MVC 中可以通過配置和實現 HandlerInterceptor 接口,來實現自己的攔截器。
1. 配置全局攔截器
在 Spring MVC 的配置文件中,添加如下配置:
<mvc:interceptors>
<bean class="com.learning.core.MyInteceptor"></bean>
</mvc:interceptors>
2. 添加攔截器實現代碼
攔截器的實現代碼如下:
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 攔截器
**/
public class MyInteceptor implements HandlerInterceptor {
// 在業務處理器處理請求之前被調用
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
// 在業務處理器處理請求完成之後,生成視圖之前執行
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
// 在 DispatcherServlet 完全處理完請求之後被調用
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
參數驗證
1. pom.xml 添加驗證依賴包
配置如下:
<!-- Hibernate 參數驗證包 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
2. 開啓註解驗證
在 Spring MVC 的配置文件中,添加如下配置信息:
<mvc:annotation-driven />
3. 編寫控制器
代碼如下:
import com.google.gson.JsonObject;
import com.learning.pojo.PersonDTO;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class PersonController {
@RequestMapping(value = "/check", produces = "text/plain;charset=utf-8")
public String check(@Validated PersonDTO person, BindingResult bindResult) {
// 需要 import com.google.gson.Gson
JsonObject result = new JsonObject();
StringBuilder errmsg = new StringBuilder();
if (bindResult.hasErrors()) {
List<ObjectError> errors = bindResult.getAllErrors();
for (ObjectError error : errors) {
errmsg.append(error.getDefaultMessage());
}
result.addProperty("status", -1);
} else {
result.addProperty("status", 1);
}
result.addProperty("errmsg", errmsg.toString());
return result.toString();
}
}
4. 編寫實體類
代碼如下:
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class PersonDTO {
@NotNull(message = "姓名不能爲空")
private String name;
@Min(value = 18,message = "年齡不能低於18歲")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
更多驗證註解,如下所示:
註解 | 運行時檢查 |
---|---|
@AssertFalse | 被註解的元素必須爲 false |
@AssertTrue | 被註解的元素必須爲 true |
@DecimalMax(value) | 被註解的元素必須爲一個數字,其值必須小於等於指定的最大值 |
@DecimalMin(Value) | 被註解的元素必須爲一個數字,其值必須大於等於指定的最小值 |
@Digits(integer=, fraction=) | 被註解的元素必須爲一個數字,其值必須在可接受的範圍內 |
@Future | 被註解的元素必須是日期,檢查給定的日期是否比現在晚 |
@Max(value) | 被註解的元素必須爲一個數字,其值必須小於等於指定的最大值 |
@Min(value) | 被註解的元素必須爲一個數字,其值必須大於等於指定的最小值 |
@NotNull | 被註解的元素必須不爲 null |
@Null | 被註解的元素必須爲 null |
@Past(java.util.Date/Calendar) | 被註解的元素必須過去的日期,檢查標註對象中的值表示的日期比當前早 |
@Pattern(regex=, flag=) | 被註解的元素必須符合正則表達式,檢查該字符串是否能夠在 match 指定的情況下被 regex 定義的正則表達式匹配 |
@Size(min=, max=) | 被註解的元素必須在制定的範圍(數據類型:String、Collection、Map、Array) |
@Valid | 遞歸的對關聯對象進行校驗, 如果關聯對象是個集合或者數組,那麼對其中的元素進行遞歸校驗,如果是一個 map,則對其中的值部分進行校驗 |
@CreditCardNumber | 對信用卡號進行一個大致的驗證 |
被註釋的元素必須是電子郵箱地址 | |
@Length(min=, max=) | 被註解的對象必須是字符串的大小必須在制定的範圍內 |
@NotBlank | 被註解的對象必須爲字符串,不能爲空,檢查時會將空格忽略 |
@NotEmpty | 被註釋的對象必須不爲空(數據:String、Collection、Map、Array) |
@Range(min=, max=) | 被註釋的元素必須在合適的範圍內(數據:BigDecimal、BigInteger、String、byte、short、int、long 和原始類型的包裝類) |
@URL(protocol=, host=, port=, regexp=, flags=) | 被註解的對象必須是字符串,檢查是否是一個有效的 URL,如果提供了 protocol、host 等,則該 URL 還需滿足提供的條件 |
5. 執行結果
執行結果,如下圖所示:
筆試面試題
1. 簡述一下 Spring MVC 的執行流程?
答:前端控制器(DispatcherServlet) 接收請求,通過映射從 IoC 容器中獲取對應的 Controller 對象和 Method 方法,在方法中進行業務邏輯處理組裝數據,組裝完數據把數據發給視圖解析器,視圖解析器根據數據和頁面信息生成最終的頁面,然後再返回給客戶端。
2. POJO 和 JavaBean 有什麼區別?
答:POJO 和 JavaBean 的區別如下:
- POJO(Plain Ordinary Java Object)普通 Java 類,具有 getter/setter 方法的普通類都就可以稱作 POJO,它是 DO/DTO/BO/VO 的統稱,禁止命名成 xxxPOJO。
- JavaBean 是 Java 語言中的一種可重用組件,JavaBean 的構造函數和行爲必須符合特定的約定:這個類必須有一個公共的缺省構造函數;這個類的屬性使用 getter/setter 來訪問,其他方法遵從標準命名規範;這個類應是可序列化的。
簡而言之,當一個 POJO 可序列化,有一個無參的構造函數,它就是一個 JavaBean。
3. 如何實現跨域訪問?
答:常見的跨域的實現方式有兩種:使用 JSONP 或者在服務器端設置運行跨域。服務器運行跨域的代碼如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 設置允許跨域的請求規則
registry.addMapping("/api/**");
}
};
}
}
4. 以下代碼描述正確的是?
@RequestMapping(value="/list",params={"age=10"}
public String list(){
// do something
}
A:age 參數不傳遞的時候,默認值是 10
B:age 參數可以爲空
C:age 參數不能爲空
D:以上都不對
答:C
題目解析:params={“age=10”} 表示必須包含 age 參數,且值必須等於 10。
5. @RequestMapping 註解的常用屬性有哪些?
答:@RequestMapping 常用屬性如下:
- value:指定 URL 請求的實際地址,用法:@RequestMapping(value="/index");
- method:指定請求的 method 類型,如 GET/POST/PUT/DELETE 等,用法:@RequestMapping(value="/list",method=RequestMethod.POST);
- params:指定請求參數中必須包含的參數名稱,如果不存在該名稱,則無法調用此方法,用法:@RequestMapping(value="/list",params={“name”,“age”})。
6. 訪問以下接口不傳遞任何參數的情況下,執行的結果是?
@RequestMapping(value="/list")
@ResponseBody
public String list(int id){
return "id="+id;
}
A:id=0
B:id=
C:頁面報錯 500
D:id=null
答:C
題目解析:頁面報錯會提示:可選的參數“id”不能轉爲 null,因爲基本類型不能賦值 null,所以會報錯。
7.訪問頁面時顯示 403 代表的含義是?
A:服務器繁忙
B:找不到該頁面
C:禁止訪問
D:服務器跳轉中
答:C
題目解析:常用 HTTP 狀態碼及對應的含義:
400:錯誤請求,服務器不理解請求的語法
401:未授權,請求要求身份驗證
403:禁止訪問,服務器拒絕請求
500:服務器內部錯誤,服務器遇到錯誤,無法完成請求
502:錯誤網關,服務器作爲網關或代理,從上游服務器收到無效響應
504:網關超時,服務器作爲網關或代理,但是沒有及時從上游服務器收到請求
8.forward 和 redirect 有什麼區別?
答:forward 和 redirect 區別如下:
- forward 表示請求轉發,請求轉發是服務器的行爲;redirect 表示重定向,重定向是客戶端行爲;
- forward 是服務器請求資源,服務器直接訪問把請求的資源轉發給瀏覽器,瀏覽器根本不知道服務器的內容是從哪來的,因此它的地址欄還是原來的地址;redirect 是服務端發送一個狀態碼告訴瀏覽器重新請求新的地址,因此地址欄顯示的是新的 URL;
- forward 轉發頁面和轉發到的頁面可以共享 request 裏面的數據;redirect 不能共享數據;
- 從效率來說,forward 比 redirect 效率更高。
9. 訪問以下接口不傳遞任何參數的情況下,執行的結果是?
@RequestMapping(value="/list")
@ResponseBody
public String list(Integer id){
return "id="+id;
}
A:id=0
B:id=
C:頁面報錯 500
D:id=null
答:D
題目解析:包裝類可以賦值 null,不會報錯。
10. Spring MVC 中如何在後端代碼中實現頁面跳轉?
答:在後端代碼中可以使用 forward:/index.jsp 或 redirect:/index.jsp 完成頁面跳轉,前者 URL 地址不會發生改變,或者 URL 地址會發生改變,完整跳轉代碼如下:
@RequestMapping("/redirect")
public String redirectTest(){
return "redirect:/index.jsp";
}
11. Spring MVC 的常用註解有哪些?
答:Spring MVC 的常用註解如下:
- @Controller:用於標記某個類爲控制器;
- @ResponseBody :標識返回的數據不是 html 標籤的頁面,而是某種格式的數據,如 JSON、XML 等;
- @RestController:相當於 @Controller 加 @ResponseBody 的組合效果;
- @Component:標識爲 Spring 的組件;
- @Configuration:用於定義配置類;
- @RequestMapping:用於映射請求地址的註解;
- @Autowired:自動裝配對象;
- @RequestHeader:可以把 Request 請求的 header 值綁定到方法的參數上。
12. 攔截器的使用場景有哪些?
答:攔截器的典型使用場景如下:
- 日誌記錄:可用於記錄請求日誌,便於信息監控和信息統計;
- 權限檢查:可用於用戶登錄狀態的檢查;
- 統一安全處理:可用於統一的安全效驗或參數的加密 / 解密等。
13. Spring MVC 如何排除攔截目錄?
答:在 Spring MVC 的配置文件中,添加 <mvc:exclude-mapping path="/static/**" />
,用於排除攔截目錄,完整配置的示例代碼如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- 排除攔截地址 -->
<mvc:exclude-mapping path="/api/**" />
<bean class="com.learning.core.MyInteceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
14.@Validated 和 @Valid 有什麼區別 ?
答:@Validated 和 @Valid 都用於參數的效驗,不同的是:
- @Valid 是 Hibernate 提供的效驗機制,Java 的 JSR 303 聲明瞭 @Valid 這個類接口,而 Hibernate-validator 對其進行了實現;@Validated 是 Spring 提供的效驗機制,@Validation 是對 @Valid 進行了二次封裝,提供了分組功能,可以在參數驗證時,根據不同的分組採用不同的驗證機制;
- @Valid 可用在成員對象的屬性字段驗證上,而 @Validated 不能用在成員對象的屬性字段驗證上,也就是說 @Validated 無法提供嵌套驗證。
15.Spring MVC 有幾種獲取 request 的方式?
答:Spring MVC 獲取 request 有以下三種方式:
① 從請求參數中獲取
@RequestMapping("/index")
@ResponseBody
public void index(HttpServletRequest request){
// do something
}
該方法實現的原理是 Controller 開始處理請求時,Spring 會將 request 對象賦值到方法參數中。
② 通過 RequestContextHolder上下文獲取 request 對象
@RequestMapping("/index")
@ResponseBody
public void index(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
// do something
}
③ 通過自動注入的方式
@Controller
public class HomeController{
@Autowired
private HttpServletRequest request; // 自動注入 request 對象
// do something
}