SpringMVC
SpringMVC是隸屬于Spring框架的一部分,主要是用來進行Web開發,是對Servlet進行了封裝。
對于SpringMVC我們主要學習如下內容:
-
SpringMVC簡介
-
請求與響應
-
REST風格
-
SSM整合(注解版)
-
攔截器
SpringMVC是處理Web層/表現層的框架,所以其主要的作用就是用來接收前端發過來的請求和數據然后經過處理并將處理的結果響應給前端,所以如何處理請求和響應是SpringMVC中非常重要的一塊內容。
REST是一種軟件架構風格,可以降低開發的復雜性,提高系統的可伸縮性,后期的應用也是非常廣泛。
SSM整合是把咱們所學習的SpringMVC+Spring+Mybatis整合在一起來完成業務開發,是對我們所學習這三個框架的一個綜合應用。
對于SpringMVC的學習,最終要達成的目標:
- 掌握基于SpringMVC獲取請求參數和響應json數據操作
- 熟練應用基于REST風格的請求路徑設置與參數傳遞
- 能夠根據實際業務建立前后端開發通信協議并進行實現
- 基于SSM整合技術開發任意業務模塊功能
1. SpringMVC概述
學習SpringMVC我們先來回顧下現在web程序是如何做的,咱們現在web程序大都基于三層架構來實現。
三層架構:
-
瀏覽器發送一個請求給后端服務器,后端服務器現在是使用Servlet來接收請求和數據
-
如果所有的處理都交給Servlet來處理的話,所有的東西都耦合在一起,對后期的維護和擴展極為不利
-
將后端服務器Servlet拆分成三層,分別是
web
、service
和dao
-
- web層主要由servlet來處理,負責頁面請求和數據的收集以及響應結果給前端
-
- service層主要負責業務邏輯的處理
-
- dao層主要負責數據的增刪改查操作
-
servlet處理請求和數據的時候,存在的問題是一個servlet只能處理一個請求
-
針對web層進行了優化,采用了MVC設計模式,將其設計為
controller
、view
和Model
-
- controller負責請求和數據的接收,接收后將其轉發給service進行業務處理
-
- service根據需要會調用dao對數據進行增刪改查
-
- dao把數據處理完后將結果交給service,service再交給controller
-
- controller根據需求組裝成Model和View,Model和View組合起來生成頁面轉發給前端瀏覽器
-
- 這樣做的好處就是controller可以處理多個請求,并對請求進行分發,執行不同的業務操作。
隨著互聯網的發展,上面的模式因為是同步調用,性能慢慢的跟不是需求,所以異步調用慢慢的走到了前臺,是現在比較流行的一種處理方式。
-
因為是異步調用,所以后端不需要返回view視圖,將其去除
-
前端如果通過異步調用的方式進行交互,后臺就需要將返回的數據轉換成json格式進行返回
-
SpringMVC主要負責的就是
-
- controller如何接收請求和數據
-
- 如何將請求和數據轉發給業務層
-
- 如何將響應數據轉換成json發回到前端
介紹了這么多,對SpringMVC進行一個定義
-
SpringMVC是一種基于Java實現MVC模型的輕量級Web框架
-
優點
-
- 使用簡單、開發便捷(相比于Servlet)
-
- 靈活性強
這里所說的優點,就需要我們在使用的過程中慢慢體會。
2.SpringMVC入門案例
2.1 SpringMVC實現流程:
SpringMVC的具體實現流程:
1.創建web工程(Maven結構)
2.設置tomcat服務器,加載web工程(tomcat插件)
3.導入坐標(SpringMVC+Servlet)
4.定義處理請求的功能類(UserController)
@Controller
public class UserController {@RequestMapping("/save")@ResponseBodypublic void save(){System.out.println("user save ...");}
}
5.設置SpringMVC的配置類
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
6.將SpringMVC設定加載到Tomcat容器中,設置請求映射(配置映射關系)
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {//加載springmvc配置類protected WebApplicationContext createServletApplicationContext() {//初始化WebApplicationContext對象AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();//加載指定配置類ctx.register(SpringMvcConfig.class);return ctx;}//設置由springmvc控制器處理的請求映射路徑protected String[] getServletMappings() {return new String[]{"/"};}//加載spring配置類protected WebApplicationContext createRootApplicationContext() {return null;}
}
對于上述的配置方式,Spring還提供了一種更簡單的配置方式,可以不用再去創建AnnotationConfigWebApplicationContext
對象,不用手動register
對應的配置類,如何實現?
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {protected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}protected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMvcConfig.class};}protected String[] getServletMappings() {return new String[]{"/"};}
}
知識點1:@Controller
名稱 | @Controller |
---|---|
類型 | 類注解 |
位置 | SpringMVC控制器類定義上方 |
作用 | 設定SpringMVC的核心控制器bean |
知識點2:@RequestMapping
名稱 | @RequestMapping |
---|---|
類型 | 類注解或方法注解 |
位置 | SpringMVC控制器類或方法定義上方 |
作用 | 設置當前控制器方法請求訪問路徑 |
相關屬性 | value(默認),請求訪問路徑 |
知識點3:@ResponseBody
名稱 | @ResponseBody |
---|---|
類型 | 類注解或方法注解 |
位置 | SpringMVC控制器類或方法定義上方 |
作用 | 設置當前控制器方法響應內容為當前返回值,無需解析 |
2.2 入門案例總結
-
一次性工作
-
- 創建工程,設置服務器,加載工程
-
- 導入坐標
-
- 創建web容器啟動類,加載SpringMVC配置,并設置SpringMVC請求攔截路徑
-
- SpringMVC核心配置類(設置配置類,掃描controller包,加載Controller控制器bean)
-
多次工作
-
- 定義處理請求的控制器類
-
- 定義處理請求的控制器方法,并配置映射路徑(@RequestMapping)與返回json數據(@ResponseBody)
2.3 工作流程解析
為了更好的使用SpringMVC,我們將SpringMVC的使用過程總共分兩個階段來分析,分別是啟動服務器初始化過程
和單次請求過程
2.3.1 啟動服務器初始化過程
- 服務器啟動,執行ServletContainersInitConfig類,初始化web容器
-
- 功能類似于以前的web.xml
- 執行createServletApplicationContext方法,創建了WebApplicationContext對象
-
- 該方法加載SpringMVC的配置類SpringMvcConfig來初始化SpringMVC的容器
-
加載SpringMvcConfig配置類
-
執行@ComponentScan加載對應的bean
-
- 掃描指定包及其子包下所有類上的注解,如Controller類上的@Controller注解
- 加載UserController,每個@RequestMapping的名稱對應一個具體的方法
-
- 此時就建立了
/save
和 save方法的對應關系
- 此時就建立了
- 執行getServletMappings方法,設定SpringMVC攔截請求的路徑規則
-
/
代表所攔截請求的路徑規則,只有被攔截后才能交給SpringMVC來處理請求
2.3.2 單次請求過程
-
發送請求
http://localhost/save
-
web容器發現該請求滿足SpringMVC攔截規則,將請求交給SpringMVC處理
-
解析請求路徑/save
-
由/save匹配執行對應的方法save()
-
- 上面的第五步已經將請求路徑和方法建立了對應關系,通過/save就能找到對應的save方法
-
執行save()
-
檢測到有@ResponseBody直接將save()方法的返回值作為響應體返回給請求方
3. 請求與響應
SpringMVC是web層的框架,主要的作用是接收請求、接收數據、響應結果,所以這一章節是學習SpringMVC的重點內容,我們主要會講解四部分內容:
-
請求映射路徑
-
請求參數
-
日期類型參數傳遞
-
響應json數據
3.1 設置請求映射路徑
@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/save")@ResponseBodypublic String save(){System.out.println("user save ...");return "{'module':'user save'}";}@RequestMapping("/delete")@ResponseBodypublic String save(){System.out.println("user delete ...");return "{'module':'user delete'}";}
}@Controller
@RequestMapping("/book")
public class BookController {@RequestMapping("/save")@ResponseBodypublic String save(){System.out.println("book save ...");return "{'module':'book save'}";}
}
3.2 請求參數
GET發送參數及中文亂碼
發送請求與參數:
接收參數:
@Controller
public class UserController {@RequestMapping("/commonParam")@ResponseBodypublic String commonParam(String name,int age){System.out.println("普通參數傳遞 name ==> "+name);System.out.println("普通參數傳遞 age ==> "+age);return "{'module':'commonParam'}";}
}
中文亂碼解決方案:
修改pom.xml
<build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.1</version><configuration><port>80</port><!--tomcat端口號--><path>/</path> <!--虛擬目錄--><uriEncoding>UTF-8</uriEncoding><!--訪問路徑編解碼字符集--></configuration></plugin></plugins></build>
POST發送參數及中文亂碼
發送請求與參數:
接收參數:
和GET一致,不用做任何修改
@Controller
public class UserController {@RequestMapping("/commonParam")@ResponseBodypublic String commonParam(String name,int age){System.out.println("普通參數傳遞 name ==> "+name);System.out.println("普通參數傳遞 age ==> "+age);return "{'module':'commonParam'}";}
}
中文亂碼解決方案:
配置過濾器(在web容器配置類中)
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[]{"/"};}//亂碼處理@Overrideprotected Filter[] getServletFilters() {CharacterEncodingFilter filter = new CharacterEncodingFilter();filter.setEncoding("UTF-8");return new Filter[]{filter};}
}
3.3 五種類型參數傳遞
常見的參數種類有:
-
普通參數
-
POJO類型參數
-
嵌套POJO類型參數
-
數組類型參數
-
集合類型參數
3.3.1 普通參數
如果形參與地址參數名不一致該如何解決?
解決方案:使用@RequestParam
注解
@RequestMapping("/commonParamDifferentName")@ResponseBodypublic String commonParamDifferentName(@RequestParam("name") String userName , int age){System.out.println("普通參數傳遞 userName ==> "+userName);System.out.println("普通參數傳遞 age ==> "+age);return "{'module':'common param different name'}";}
注意:寫上@RequestParam注解框架就不需要自己去解析注入,能提升框架處理性能
3.3.2 POJO數據類型
簡單數據類型一般處理的是參數個數比較少的請求,如果參數比較多,那么后臺接收參數的時候就比較復雜,這個時候我們可以考慮使用POJO數據類型。
- POJO參數:請求參數名與形參對象屬性名相同,定義POJO類型形參即可接收參數
此時需要使用前面準備好的POJO類,先來看下User
public class User {private String name;private int age;//setter...getter...略
}
發送請求和參數:
后臺接收參數:
//POJO參數:請求參數與形參對象中的屬性對應即可完成參數傳遞
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){System.out.println("pojo參數傳遞 user ==> "+user);return "{'module':'pojo param'}";
}
注意:
- POJO參數接收,前端GET和POST發送請求數據的方式不變。
- 請求參數key的名稱要和POJO中屬性的名稱一致,否則無法封裝。
3.3.3 嵌套POJO類型參數
如果POJO對象中嵌套了其他的POJO類,如
public class Address {private String province;private String city;//setter...getter...略
}
public class User {private String name;private int age;private Address address;//setter...getter...略
}
- 嵌套POJO參數:請求參數名與形參對象屬性名相同,按照對象層次結構關系即可接收嵌套POJO屬性參數
發送請求和參數:
后臺接收參數:
//POJO參數:請求參數與形參對象中的屬性對應即可完成參數傳遞
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){System.out.println("pojo參數傳遞 user ==> "+user);return "{'module':'pojo param'}";
}
注意:
請求參數key的名稱要和POJO中屬性的名稱一致,否則無法封裝
3.3.4 數組類型參數
- 數組參數:請求參數名與形參對象屬性名相同且請求參數為多個,定義數組類型即可接收參數
發送請求和參數:
后臺接收參數:
//數組參數:同名請求參數可以直接映射到對應名稱的形參數組對象中@RequestMapping("/arrayParam")@ResponseBodypublic String arrayParam(String[] likes){System.out.println("數組參數傳遞 likes ==> "+ Arrays.toString(likes));return "{'module':'array param'}";}
3.3.5 集合類型參數
發送請求和參數:
后端接收參數:
使用@RequestParam
注解
//集合參數:同名請求參數可以使用@RequestParam注解映射到對應名稱的集合對象中作為數據
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){System.out.println("集合參數傳遞 likes ==> "+ likes);return "{'module':'list param'}";
}
如果不加@RequestParam
運行會報錯,
錯誤的原因是:SpringMVC將List看做是一個POJO對象來處理,將其創建一個對象并準備把前端的數據封裝到對象中,但是List是一個接口無法創建對象,所以報錯。
-
集合保存普通參數:請求參數名與形參集合對象名相同且請求參數為多個,@RequestParam綁定參數關系
-
對于簡單數據類型使用數組會比集合更簡單些。
知識點1:@RequestParam
名稱 | @RequestParam |
---|---|
類型 | 形參注解 |
位置 | SpringMVC控制器方法形參定義前面 |
作用 | 綁定請求參數與處理器方法形參間的關系 |
相關參數 | required:是否為必傳參數 defaultValue:參數默認值 |
3.4 JSON數據傳輸參數
現在比較流行的開發方式為異步調用。前后臺以異步方式進行交換,傳輸的數據使用的是JSON,
對于JSON數據類型,我們常見的有三種:
-
json普通數組([“value1”,“value2”,“value3”,…])
-
json對象({key1:value1,key2:value2,…})
-
json對象數組([{key1:value1,…},{key2:value2,…}])
對于上述數據,前端如何發送,后端如何接收?
3.4.1 JSON普通數組
步驟:
-
添加jackson依賴(用來處理json的轉換)
-
postman發送JSON數據
- 開啟SpringMVC注解支持
@EnableWebMvc
,其中一個功能就是支持將JSON轉換成對象
@Configuration
@ComponentScan("com.itheima.controller")
//開啟json數據類型自動轉換
@EnableWebMvc
public class SpringMvcConfig {
}
- 參數前添加
@RequsetBody
//使用@RequestBody注解將外部傳遞的json數組數據映射到形參的集合對象中作為數據
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){System.out.println("list common(json)參數傳遞 list ==> "+likes);return "{'module':'list common for json param'}";
}
3.4.2 JSON對象數據
請求和數據的發送:
后端接收數據:
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){System.out.println("pojo(json)參數傳遞 user ==> "+user);return "{'module':'pojo for json param'}";
}
3.4.3 JSON對象數組
請求和數據的發送:
后端接收數據:
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){System.out.println("list pojo(json)參數傳遞 list ==> "+list);return "{'module':'list pojo for json param'}";
}
小結
SpringMVC接收JSON數據的實現步驟為:
(1)導入jackson包
(2)使用PostMan發送JSON數據
(3)開啟SpringMVC注解驅動,在配置類上添加@EnableWebMvc注解
(4)Controller方法的參數前添加@RequestBody注解
知識點1:@EnableWebMvc
名稱 | @EnableWebMvc |
---|---|
類型 | 配置類注解 |
位置 | SpringMVC配置類定義上方 |
作用 | 開啟SpringMVC多項輔助功能(轉換JSON數據等) |
知識點2:@RequestBody
名稱 | @RequestBody |
---|---|
類型 | 形參注解 |
位置 | SpringMVC控制器方法形參定義前面 |
作用 | 將請求中請求體所包含的數據傳遞給請求參數,此注解一個處理器方法只能使用一次 |
@RequestBody與@RequestParam區別
-
區別
-
- @RequestParam用于接收url地址傳參,表單傳參【application/x-www-form-urlencoded】(主要是針對形參名與請求參數名不一致,以及傳遞集合的情況)
-
- @RequestBody用于接收json數據【application/json】
-
應用
-
- 后期開發中,發送json格式數據為主,@RequestBody應用較廣
-
- 如果發送非json格式數據,選用@RequestParam接收請求參數
3.5 日期類型參數傳遞
對于日期的格式有N多中輸入方式,比如:
-
2088-08-18
-
2088/08/18
-
08/18/2088
-
…
前端發送請求和參數:
后端接收日期類型的參數:
使用@DateTimeFormat
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,@DateTimeFormat(pattern="yyyy-MM-dd") Date date1,@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2)System.out.println("參數傳遞 date ==> "+date);System.out.println("參數傳遞 date1(yyyy-MM-dd) ==> "+date1);System.out.println("參數傳遞 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);return "{'module':'data param'}";
}
知識點1:@DateTimeFormat
名稱 | @DateTimeFormat |
---|---|
類型 | 形參注解 |
位置 | SpringMVC控制器方法形參前面 |
作用 | 設定日期時間型數據格式 |
相關屬性 | pattern:指定日期時間格式字符串 |
3.6 響應
對于響應,主要就包含兩部分內容:
-
響應頁面
-
響應數據
-
- 文本數據
-
- json數據
3.6.1 響應頁面[了解]
@Controller
public class UserController {@RequestMapping("/toJumpPage")//注意//1.此處不能添加@ResponseBody,如果加了該注入,會直接將page.jsp當字符串返回前端//2.方法需要返回Stringpublic String toJumpPage(){System.out.println("跳轉頁面");//此處應該是轉發操作return "page.jsp";}}
3.6.2 返回文本數據[了解]
@Controller
public class UserController {@RequestMapping("/toText")//注意此處該注解就不能省略,如果省略了,會把response text當前頁面名稱去查找,如果沒有回報404錯誤@ResponseBodypublic String toText(){System.out.println("返回純文本數據");return "response text";}}
3.6.3 響應JSON數據
響應POJO對象
@Controller
public class UserController {@RequestMapping("/toJsonPOJO")@ResponseBodypublic User toJsonPOJO(){System.out.println("返回json對象數據");User user = new User();user.setName("itcast");user.setAge(15);return user;}}
返回值為實體類對象,設置返回值為實體類類型,即可實現返回對應對象的json數據,需要依賴==@ResponseBody注解和@EnableWebMvc==注解
響應POJO集合對象
@Controller
public class UserController {@RequestMapping("/toJsonList")@ResponseBodypublic List<User> toJsonList(){System.out.println("返回json集合數據");User user1 = new User();user1.setName("傳智播客");user1.setAge(15);User user2 = new User();user2.setName("黑馬程序員");user2.setAge(12);List<User> userList = new ArrayList<User>();userList.add(user1);userList.add(user2);return userList;}}
知識點1:@ResponseBody
名稱 | @ResponseBody |
---|---|
類型 | 方法\類注解 |
位置 | SpringMVC控制器方法定義上方和控制類上 |
作用 | 設置當前控制器返回值作為響應體, 寫在類上,該類的所有方法都有該注解功能 |
相關屬性 | pattern:指定日期時間格式字符串 |
-
當方法上有@ReponseBody注解后
-
- 方法的返回值為字符串,會將其作為文本內容直接響應給前端
-
- 方法的返回值為對象,會將對象轉換成JSON響應給前端
4.Rest風格
對于Rest風格,我們需要學習的內容包括:
-
REST簡介
-
REST入門案例
-
REST快速開發
-
案例:基于RESTful頁面數據交互
4.1REST簡介
-
REST(Representational State Transfer),表現形式狀態轉換,它是一種軟件架構風格
當我們想表示一個網絡資源的時候,可以使用兩種方式: -
- 傳統風格資源描述形式
-
-
http://localhost/user/getById?id=1
查詢id為1的用戶信息
-
-
-
http://localhost/user/saveUser
保存用戶信息
-
-
- REST風格描述形式
-
-
http://localhost/user/1
-
-
-
http://localhost/user
-
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(刪除)
PS:根據REST風格對資源(后臺服務)進行訪問稱為RESTful。
4.2 RestFul入門案例
1.創建web的maven項目,導入坐標
2.創建servlet,springmvc配置類
3.編寫pojo類Book
4.編寫BookController控制器類
部分代碼如下:
save方法:
@RequestMapping(value = "/users",method = RequestMethod.POST)@ResponseBodypublic String save(@RequestBody Book book){System.out.println("book save..." + book);return "{'module':'book save'}";}
delete方法:
后端獲取參數,需要做如下修改:
● 修改@RequestMapping的value屬性,將其中修改為/users/{id},目的是和路徑匹配
● 在方法的形參前添加@PathVariable注解
@Controller
public class UserController {//設置當前請求方法為DELETE,表示REST風格中的刪除操作@RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)@ResponseBodypublic String delete(@PathVariable Integer id,@PathVariable String name) {System.out.println("user delete..." + id+","+name);return "{'module':'user delete'}";}
}
小結
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
關于接收參數,我們學過三個注解@RequestBody
、@RequestParam
、@PathVariable
,這三個注解之間的區別和應用分別是什么?
-
區別
-
- @RequestParam用于接收url地址傳參或表單傳參
-
- @RequestBody用于接收json數據(請求體)
-
- @PathVariable用于接收路徑參數,使用 {參數名稱} 描述路徑參數
-
應用
-
- 后期開發中,發送請求參數超過1個時,以json格式為主,@RequestBody應用較廣
-
- 如果發送非json格式數據,選用@RequestParam接收請求參數
-
- 采用RESTful進行開發,當參數數量較少時,例如1個,可以采用@PathVariable接收請求路徑變量,通常用于傳遞id值
4.3RestFul快速開發
知識點1:@RestController
名稱 | @RestController |
---|---|
類型 | 類注解 |
位置 | 基于SpringMVC的RESTful開發控制器類定義上方 |
作用 | 設置當前控制器類為RESTful風格, 等同于@Controller與@ResponseBody兩個注解組合功能 |
知識點2:@GetMapping @PostMapping @PutMapping @DeleteMapping
名稱 | @GetMapping @PostMapping @PutMapping @DeleteMapping |
---|---|
類型 | 方法注解 |
位置 | 基于SpringMVC的RESTful開發控制器方法定義上方 |
作用 | 設置當前控制器方法請求訪問路徑與請求動作,每種對應一個請求動作, 例如@GetMapping對應GET請求 |
相關屬性 | value(默認):請求訪問路徑 |
4.4RestFul實例
后端:
編寫Controller類并使用RESTful進行配置:
@RestController
@RequestMapping("/books")
public class BookController {@PostMappingpublic String save(@RequestBody Book book){System.out.println("book save ==> "+ book);return "{'module':'book save success'}";}@GetMappingpublic List<Book> getAll(){System.out.println("book getAll is running ...");List<Book> bookList = new ArrayList<Book>();Book book1 = new Book();book1.setType("計算機");book1.setName("SpringMVC入門教程");book1.setDescription("小試牛刀");bookList.add(book1);Book book2 = new Book();book2.setType("計算機");book2.setName("SpringMVC實戰教程");book2.setDescription("一代宗師");bookList.add(book2);Book book3 = new Book();book3.setType("計算機叢書");book3.setName("SpringMVC實戰教程進階");book3.setDescription("一代宗師嘔心創作");bookList.add(book3);return bookList;}
}
前端:
想要訪問靜態資源時,由于SpringMVC攔截靜態資源,導致無法訪問:
(1)出現錯誤的原因?
SpringMVC攔截了靜態資源,根據/pages/books.html去controller找對應的方法,找不到所以會報404的錯誤。
(2)SpringMVC為什么會攔截靜態資源呢?
(3)解決方案?
- SpringMVC需要將靜態資源進行放行。
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {//設置靜態資源訪問過濾,當前類需要設置為配置類,并被掃描加載@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {//當訪問/pages/????時候,從/pages目錄下查找內容registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");registry.addResourceHandler("/js/**").addResourceLocations("/js/");registry.addResourceHandler("/css/**").addResourceLocations("/css/");registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");}
}
- 該配置類是在config目錄下,SpringMVC掃描的是controller包,所以該配置類還未生效,要想生效需要將SpringMvcConfig配置類進行修改
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {
}或者@Configuration
@ComponentScan("com.itheima")
@EnableWebMvc
public class SpringMvcConfig {
}
最后由html文件發送ajax異步請求調用controller的各個方法即可
//添加saveBook () {axios.post("/books",this.formData).then((res)=>{});},//主頁列表查詢getAll() {axios.get("/books").then((res)=>{this.dataList = res.data;});}
5.SSM整合
前面我們已經把Mybatis
、Spring
和SpringMVC
三個框架進行了學習,今天主要的內容就是把這三個框架整合在一起完成我們的業務功能開發,具體如何來整合,我們一步步來學習。
流程分析:
5.1 創建工程
-
創建一個Maven的web工程
-
pom.xml添加SSM需要的依賴jar包
<?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_08_ssm</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.2.10.RELEASE</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></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>
-
編寫Web項目的入口配置類
ServletConfig
,實現AbstractAnnotationConfigDispatcherServletInitializer
重寫以下方法 -
- getRootConfigClasses() :返回Spring的配置類->需要SpringConfig配置類
-
- getServletConfigClasses() :返回SpringMVC的配置類->需要SpringMvcConfig配置類
-
- getServletMappings() : 設置SpringMVC請求攔截路徑規則
-
- getServletFilters() :設置過濾器,解決POST請求中文亂碼問題
5.2 SSM整合[重點是各個配置的編寫]
-
SpringConfig
-
- 標識該類為配置類 @Configuration
-
- 掃描Service所在的包 @ComponentScan
-
- 在Service層要管理事務 @EnableTransactionManagement
-
- 讀取外部的properties配置文件 @PropertySource
-
-
整合Mybatis需要引入Mybatis相關配置類 @Import
-
-
-
- 第三方數據源配置類 JdbcConfig
-
-
-
-
- 構建DataSource數據源,DruidDataSouroce,需要注入數據庫連接四要素, @Bean @Value
-
-
-
-
-
-
構建平臺事務管理器,DataSourceTransactionManager,@Bean
-
-
-
-
-
- Mybatis配置類 MybatisConfig
-
-
-
-
- 構建SqlSessionFactoryBean并設置別名掃描與數據源,@Bean
-
-
-
-
-
-
構建MapperScannerConfigurer并設置DAO層的包掃描 ,創建mapper接口的自動代理對象,@Bean
-
-
-
-
SpringMvcConfig
-
- 標識該類為配置類 @Configuration
-
- 掃描Controller所在的包 @ComponentScan
-
- 開啟SpringMVC注解支持 @EnableWebMvc ,開啟如轉換json數據等多種輔助功能
5.3 功能模塊[與具體的業務模塊有關]
-
創建數據庫表
-
根據數據庫表創建對應的模型類pojo
-
通過Dao層完成數據庫表的增刪改查(接口+自動代理)
-
編寫Service層[Service接口+實現類]
-
- @Service
-
- @Transactional
-
-
?
-
- 整合Junit對業務層進行單元測試
-
-
- @RunWith
-
-
-
- @ContextConfiguration
-
-
-
- @Test
-
-
-
編寫Controller層
-
- 接收請求 @RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping
-
- 接收數據 簡單、POJO、嵌套POJO、集合、數組、JSON數據類型
-
-
- @RequestParam
-
-
-
- @PathVariable
-
-
-
- @RequestBody
-
-
- 轉發業務層
-
-
- @Autowired
-
-
- 響應結果
-
-
- @ResponseBody
-
PS:SSM整合完之后就可以進行junit測試service類以及postman測試controller類
6.統一結果封裝
SSM整合以及功能模塊開發完成后,接下來,我們在上述案例的基礎上分析下有哪些問題需要我們去解決下。首先第一個問題是:
隨著業務的增長,我們需要返回的數據類型會越來越多。對于前端開發人員在解析數據的時候就比較凌亂了,所以對于前端來說,如果后臺能夠返回一個統一的數據結果,前端在解析的時候就可以按照一種方式進行解析。開發就會變得更加簡單。
所以我們就想能不能將返回結果的數據進行統一,具體如何來做,大體的思路為:
-
為了封裝返回的結果數據:創建結果模型類,封裝數據到data屬性中
-
為了封裝返回的數據是何種操作及是否操作成功:封裝操作結果到code屬性中
-
操作失敗后為了封裝返回的錯誤信息:封裝特殊消息到message(msg)屬性中
根據分析,我們可以設置統一數據返回結果類
1.Result:
public class Result {//描述統一格式中的數據private Object data;//描述統一格式中的編碼,用于區分操作,可以簡化配置0或1表示成功失敗private Integer code;//描述統一格式中的消息,可選屬性private String msg;public Result() {}//構造方法是方便對象的創建public Result(Integer code,Object data) {this.data = data;this.code = code;}//構造方法是方便對象的創建public Result(Integer code, Object data, String msg) {this.data = data;this.code = code;this.msg = msg;}//setter...getter...省略
}
**注意:**Result類名及類中的字段并不是固定的,可以根據需要自行增減提供若干個構造方法,方便操作。
2.Code:
//狀態碼
public class Code {public static final Integer SAVE_OK = 20011;public static final Integer DELETE_OK = 20021;public static final Integer UPDATE_OK = 20031;public static final Integer GET_OK = 20041;public static final Integer SAVE_ERR = 20010;public static final Integer DELETE_ERR = 20020;public static final Integer UPDATE_ERR = 20030;public static final Integer GET_ERR = 20040;
}
**注意:**code類中的常量設計也不是固定的,可以根據需要自行增減,例如將查詢再進行細分為GET_OK,GET_ALL_OK,GET_PAGE_OK等。
3.修改Controller類的返回值為Result
//統一每一個控制器方法返回值
@RestController
@RequestMapping("/books")
public class BookController {@Autowiredprivate BookService bookService;@PostMappingpublic Result save(@RequestBody Book book) {boolean flag = bookService.save(book);return new Result(flag ? Code.SAVE_OK:Code.SAVE_ERR,flag);}@PutMappingpublic Result update(@RequestBody Book book) {boolean flag = bookService.update(book);return new Result(flag ? Code.UPDATE_OK:Code.UPDATE_ERR,flag);}@DeleteMapping("/{id}")public Result delete(@PathVariable Integer id) {boolean flag = bookService.delete(id);return new Result(flag ? Code.DELETE_OK:Code.DELETE_ERR,flag);}@GetMapping("/{id}")public Result getById(@PathVariable Integer id) {Book book = bookService.getById(id);Integer code = book != null ? Code.GET_OK : Code.GET_ERR;String msg = book != null ? "" : "數據查詢失敗,請重試!";return new Result(code,book,msg);}@GetMappingpublic Result getAll() {List<Book> bookList = bookService.getAll();Integer code = bookList != null ? Code.GET_OK : Code.GET_ERR;String msg = bookList != null ? "" : "數據查詢失敗,請重試!";return new Result(code,bookList,msg);}
}
至此,我們的返回結果就已經能以一種統一的格式返回給前端。前端根據返回的結果,先從中獲取code
,根據code判斷,如果成功則取data
屬性的值,如果失敗,則取msg
中的值做提示。
7.統一異常處理
先來看下異常的種類及出現異常的原因:
-
框架內部拋出的異常:因使用不合規導致
-
數據層拋出的異常:因外部服務器故障導致(例如:服務器訪問超時)
-
業務層拋出的異常:因業務邏輯書寫錯誤導致(例如:遍歷業務書寫操作,導致索引異常等)
-
表現層拋出的異常:因數據收集、校驗等規則導致(例如:不匹配的數據類型間導致異常)
-
工具類拋出的異常:因工具類書寫不嚴謹不夠健壯導致(例如:必要釋放的連接長期未釋放等)
思考
-
各個層級均出現異常,異常處理代碼書寫在哪一層?
所有的異常均拋出到表現層進行處理 -
異常的種類很多,表現層如何將所有的異常都處理到呢?
異常分類 -
表現層處理異常,每個方法中單獨書寫,代碼書寫量巨大且意義不強,如何解決?
AOP
7.1異常處理器
對于上面這些問題及解決方案,SpringMVC已經為我們提供了一套解決方案:
-
異常處理器:
-
-
集中的、統一的處理項目中出現的異常,并返回數據給前端
-
知識點1:@RestControllerAdvice
名稱 | @RestControllerAdvice |
---|---|
類型 | 類注解 |
位置 | Rest風格開發的控制器增強類定義上方 |
作用 | 為Rest風格開發的控制器類做增強 |
知識點2:@ExceptionHandler
名稱 | @ExceptionHandler |
---|---|
類型 | 方法注解 |
位置 | 專用于異常處理的控制器方法上方 |
作用 | 設置指定異常的處理方案,功能等同于控制器方法, 出現異常后終止原始控制器執行,并轉入當前方法執行 |
**說明:**此類方法可以根據處理的異常不同,制作多個方法分別處理對應的異常
7.2項目異常處理方案
7.2.1異常分類
異常處理器我們已經能夠使用了,那么在咱們的項目中該如何來處理異常呢?
因為異常的種類有很多,如果每一個異常都對應一個@ExceptionHandler,那得寫多少個方法來處理各自的異常,所以我們在處理異常之前,需要對異常進行一個分類:
-
業務異常(BusinessException)
-
- 規范的用戶行為產生的異常
-
-
- 用戶在頁面輸入內容的時候未按照指定格式進行數據填寫,如在年齡框輸入的是字符串
- 用戶在頁面輸入內容的時候未按照指定格式進行數據填寫,如在年齡框輸入的是字符串
-
-
- 不規范的用戶行為操作產生的異常
-
-
- 如用戶故意傳遞錯誤數據
- 如用戶故意傳遞錯誤數據
-
-
系統異常(SystemException)
-
- 項目運行過程中可預計但無法避免的異常
-
-
- 比如數據庫或服務器宕機
-
-
其他異常(Exception)
-
- 編程人員未預期到的異常,如:用到的文件不存在
- 編程人員未預期到的異常,如:用到的文件不存在
將異常分類以后,針對不同類型的異常,要提供具體的解決方案:
7.2.2異常解決方案
-
業務異常(BusinessException)
-
- 發送對應消息傳遞給用戶,提醒規范操作
-
-
- 大家常見的就是提示用戶名已存在或密碼格式不正確等
-
-
系統異常(SystemException)
-
- 發送固定消息傳遞給用戶,安撫用戶
-
-
- 系統繁忙,請稍后再試
-
-
-
- 系統正在維護升級,請稍后再試
-
-
-
- 系統出問題,請聯系系統管理員等
-
-
- 發送特定消息給運維人員,提醒維護
-
-
- 可以發送短信、郵箱或者是公司內部通信軟件
-
-
- 記錄日志
-
-
- 發消息和記錄日志對用戶來說是不可見的,屬于后臺程序
-
-
其他異常(Exception)
-
- 發送固定消息傳遞給用戶,安撫用戶
-
- 發送特定消息給編程人員,提醒維護(納入預期范圍內)
-
-
- 一般是程序沒有考慮全,比如未做非空校驗等
-
-
- 記錄日志
7.2.3異常解決方案的具體表現
步驟1:自定義異常類
//自定義異常處理器,用于封裝異常信息,對異常進行分類
public class SystemException extends RuntimeException{private Integer code;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public SystemException(Integer code, String message) {super(message);this.code = code;}public SystemException(Integer code, String message, Throwable cause) {super(message, cause);this.code = code;}}//自定義異常處理器,用于封裝異常信息,對異常進行分類
public class BusinessException extends RuntimeException{private Integer code;public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public BusinessException(Integer code, String message) {super(message);this.code = code;}public BusinessException(Integer code, String message, Throwable cause) {super(message, cause);this.code = code;}}
說明:
-
讓自定義異常類繼承
RuntimeException
的好處是,后期在拋出這兩個異常的時候,就不用在try…catch…或throws了 -
自定義異常類中添加
code
屬性的原因是為了更好的區分異常是來自哪個業務的
步驟2:將其他異常包成自定義異常
假如在BookServiceImpl的getById方法拋異常了,該如何來包裝呢?
public Book getById(Integer id) {//模擬業務異常,包裝成自定義異常if(id == 1){throw new BusinessException(Code.BUSINESS_ERR,"請不要使用你的技術挑戰我的耐性!");}//模擬系統異常,將可能出現的異常進行包裝,轉換成自定義異常try{int i = 1/0;}catch (Exception e){throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服務器訪問超時,請重試!",e);}return bookDao.getById(id);
}
具體的包裝方式有:
-
方式一:
try{}catch(){}
在catch中重新throw我們自定義異常即可。 -
方式二:直接throw自定義異常即可
上面為了使code
看著更專業些,我們在Code類中再新增需要的屬性
//狀態碼
public class Code {public static final Integer SAVE_OK = 20011;public static final Integer DELETE_OK = 20021;public static final Integer UPDATE_OK = 20031;public static final Integer GET_OK = 20041;public static final Integer SAVE_ERR = 20010;public static final Integer DELETE_ERR = 20020;public static final Integer UPDATE_ERR = 20030;public static final Integer GET_ERR = 20040;public static final Integer SYSTEM_ERR = 50001;public static final Integer SYSTEM_TIMEOUT_ERR = 50002;public static final Integer SYSTEM_UNKNOW_ERR = 59999;public static final Integer BUSINESS_ERR = 60002;
}
步驟3:處理器類中處理自定義異常
//@RestControllerAdvice用于標識當前類為REST風格對應的異常處理器
@RestControllerAdvice
public class ProjectExceptionAdvice {//@ExceptionHandler用于設置當前處理器類對應的異常類型@ExceptionHandler(SystemException.class)public Result doSystemException(SystemException ex){//記錄日志//發送消息給運維//發送郵件給開發人員,ex對象發送給開發人員return new Result(ex.getCode(),null,ex.getMessage());}@ExceptionHandler(BusinessException.class)public Result doBusinessException(BusinessException ex){return new Result(ex.getCode(),null,ex.getMessage());}//除了自定義的異常處理器,保留對Exception類型的異常處理,用于處理非預期的異常@ExceptionHandler(Exception.class)public Result doOtherException(Exception ex){//記錄日志//發送消息給運維//發送郵件給開發人員,ex對象發送給開發人員return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系統繁忙,請稍后再試!");}
}
8.前后臺協議協調
項目結構:
<!DOCTYPE html><html><head><!-- 頁面meta --><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>SpringMVC案例</title><meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport"><!-- 引入樣式 --><link rel="stylesheet" href="../plugins/elementui/index.css"><link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css"><link rel="stylesheet" href="../css/style.css"></head><body class="hold-transition"><div id="app"><div class="content-header"><h1>圖書管理</h1></div><div class="app-container"><div class="box"><div class="filter-container"><el-input placeholder="圖書名稱" v-model="pagination.queryString" style="width: 200px;" class="filter-item"></el-input><el-button @click="getAll()" class="dalfBut">查詢</el-button><el-button type="primary" class="butT" @click="handleCreate()">新建</el-button></div><el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row><el-table-column type="index" align="center" label="序號"></el-table-column><el-table-column prop="type" label="圖書類別" align="center"></el-table-column><el-table-column prop="name" label="圖書名稱" align="center"></el-table-column><el-table-column prop="description" label="描述" align="center"></el-table-column><el-table-column label="操作" align="center"><template slot-scope="scope"><el-button type="primary" size="mini" @click="handleUpdate(scope.row)">編輯</el-button><el-button type="danger" size="mini" @click="handleDelete(scope.row)">刪除</el-button></template></el-table-column></el-table><!-- 新增標簽彈層 --><div class="add-form"><el-dialog title="新增圖書" :visible.sync="dialogFormVisible"><el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px"><el-row><el-col :span="12"><el-form-item label="圖書類別" prop="type"><el-input v-model="formData.type"/></el-form-item></el-col><el-col :span="12"><el-form-item label="圖書名稱" prop="name"><el-input v-model="formData.name"/></el-form-item></el-col></el-row><el-row><el-col :span="24"><el-form-item label="描述"><el-input v-model="formData.description" type="textarea"></el-input></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogFormVisible = false">取消</el-button><el-button type="primary" @click="handleAdd()">確定</el-button></div></el-dialog></div><!-- 編輯標簽彈層 --><div class="add-form"><el-dialog title="編輯檢查項" :visible.sync="dialogFormVisible4Edit"><el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px"><el-row><el-col :span="12"><el-form-item label="圖書類別" prop="type"><el-input v-model="formData.type"/></el-form-item></el-col><el-col :span="12"><el-form-item label="圖書名稱" prop="name"><el-input v-model="formData.name"/></el-form-item></el-col></el-row><el-row><el-col :span="24"><el-form-item label="描述"><el-input v-model="formData.description" type="textarea"></el-input></el-form-item></el-col></el-row></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogFormVisible4Edit = false">取消</el-button><el-button type="primary" @click="handleEdit()">確定</el-button></div></el-dialog></div></div></div></div></body><!-- 引入組件庫 --> <script src="../js/vue.js"></script><script src="../plugins/elementui/index.js"></script><script type="text/javascript" src="../js/jquery.min.js"></script><script src="../js/axios-0.18.0.js"></script><!-- 重點!!!!!!--><script>var vue = new Vue({el: '#app',data:{pagination: {},dataList: [],//當前頁要展示的列表數據formData: {},//表單數據dialogFormVisible: false,//控制表單是否可見dialogFormVisible4Edit:false,//編輯表單是否可見rules: {//校驗規則type: [{ required: true, message: '圖書類別為必填項', trigger: 'blur' }],name: [{ required: true, message: '圖書名稱為必填項', trigger: 'blur' }]}},//鉤子函數,VUE對象初始化完成后自動執行created() {this.getAll();},methods: {//列表getAll() {//發送ajax請求axios.get("/books").then((res)=>{this.dataList = res.data.data;});},//彈出添加窗口handleCreate() {this.dialogFormVisible = true;this.resetForm();},//重置表單resetForm() {this.formData = {};},//添加handleAdd () {//發送ajax請求axios.post("/books",this.formData).then((res)=>{console.log(res.data);//如果操作成功,關閉彈層,顯示數據if(res.data.code == 20011){this.dialogFormVisible = false;this.$message.success("添加成功");}else if(res.data.code == 20010){this.$message.error("添加失敗");}else{//服務器異常等this.$message.error(res.data.msg);}}).finally(()=>{this.getAll();});},//彈出編輯窗口handleUpdate(row) {// console.log(row); //row.id 查詢條件//查詢數據,根據id查詢axios.get("/books/"+row.id).then((res)=>{// console.log(res.data.data);if(res.data.code == 20041){//展示彈層,加載數據this.formData = res.data.data;this.dialogFormVisible4Edit = true;}else{this.$message.error(res.data.msg);}});},//編輯handleEdit() {//發送ajax請求axios.put("/books",this.formData).then((res)=>{//如果操作成功,關閉彈層,顯示數據if(res.data.code == 20031){this.dialogFormVisible4Edit = false;this.$message.success("修改成功");}else if(res.data.code == 20030){this.$message.error("修改失敗");}else{this.$message.error(res.data.msg);}}).finally(()=>{this.getAll();});},// 刪除handleDelete(row) {//1.彈出提示框this.$confirm("此操作永久刪除當前數據,是否繼續?","提示",{type:'info'}).then(()=>{//2.做刪除業務axios.delete("/books/"+row.id).then((res)=>{if(res.data.code == 20021){this.$message.success("刪除成功");}else{this.$message.error("刪除失敗");}}).finally(()=>{this.getAll();});}).catch(()=>{//3.取消刪除this.$message.info("取消刪除操作");});}}})</script></html>
9.攔截器interceptor
瀏覽器訪問資源路徑流程:
(1)瀏覽器發送一個請求會先到Tomcat的web服務器
(2)Tomcat服務器接收到請求以后,會去判斷請求的是靜態資源還是動態資源
(3)如果是靜態資源,會直接到Tomcat的項目部署目錄下去直接訪問
(4)如果是動態資源,就需要交給項目的后臺代碼進行處理
(5)在找到具體的方法之前,我們可以去配置過濾器(可以配置多個),按照順序進行執行
(6)然后進入到到中央處理器(SpringMVC中的內容),SpringMVC會根據配置的規則進行攔截
(7)如果滿足規則,則進行處理,找到其對應的controller類中的方法進行執行,完成后返回結果
(8)如果不滿足規則,則不進行處理
(9)這個時候,如果我們需要在每個Controller方法執行的前后添加業務,具體該如何來實現?
這個就是攔截器要做的事。
9.1 攔截器概念
-
攔截器(Interceptor)是一種動態攔截方法調用的機制,在SpringMVC中動態攔截控制器方法的執行
-
作用:
-
- 在指定的方法調用前后執行預先設定的代碼
-
- 阻止原始方法的執行
-
- 總結:攔截器就是用來做增強(底層就是AOP)
攔截器和過濾器之間的區別是什么?
-
歸屬不同:Filter屬于Servlet技術,Interceptor屬于SpringMVC技術
-
攔截內容不同:Filter對所有訪問進行增強,Interceptor僅針對SpringMVC的訪問進行增強
9.2 攔截器入門案例
項目結構:
1.創建攔截器類
@Component
//定義攔截器類,實現HandlerInterceptor接口
//注意當前類必須受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {@Override//原始方法調用前執行的內容public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...");return true;}@Override//原始方法調用后執行的內容public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...");}@Override//原始方法調用完成后執行的內容public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...");}
}
**注意:**攔截器類要被SpringMVC容器掃描到。
2.配置攔截器類
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {@Autowiredprivate ProjectInterceptor projectInterceptor;@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");}@Overrideprotected void addInterceptors(InterceptorRegistry registry) {//配置攔截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*" );}
}
**注意:**此處也可以簡化SpringMvcSupport的編寫,直接讓SpringMvcConfig實現WebMvcConfigurer接口即可,此后就不用再寫SpringMvcSupport類了
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//實現WebMvcConfigurer接口可以簡化開發,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {@Autowiredprivate ProjectInterceptor projectInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置多攔截器registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");}
}
攔截器執行流程
ps:handle[controller的方法]
9.3 攔截器參數
preHandle
原始方法之前運行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HandlerMethod hm = (HandlerMethod)handler;String methodName = hm.getMethod().getName();//可以獲取方法的名稱System.out.println("preHandle..."+methodName);return true;
}
-
request:請求對象
-
response:響應對象
-
handler:被調用的處理器對象,本質上是一個方法對象[HandleMethod],對反射中的Method對象進行了再包裝
postHandle
原始方法運行后運行,如果原始方法被攔截,則不執行
public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception {System.out.println("postHandle");
}
前三個參數和上面的是一致的。
modelAndView:如果處理器執行完成具有返回結果,可以讀取到對應數據與頁面信息,并進行調整
因為咱們現在都是返回json數據,所以該參數的使用率不高。
afterCompletion
攔截器最后執行的方法,無論原始方法是否執行
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) throws Exception {System.out.println("afterCompletion");
}
前三個參數與上面的是一致的。
ex:如果處理器執行過程中出現異常對象,可以針對異常情況進行單獨處理
因為我們現在已經有全局異常處理器類,所以該參數的使用率也不高。
這三個方法中,最常用的是preHandle,在這個方法中可以通過返回值來決定是否要進行放行,我們可以把業務邏輯放在該方法中,如果滿足業務則返回true放行,不滿足則返回false攔截。
9.4 攔截器鏈配置
preHandle:與配置順序相同,必定運行
postHandle:與配置順序相反,可能不運行
afterCompletion:與配置順序相反,可能不運行。
這個順序不太好記,最終只需要把握住一個原則即可:以最終的運行結果為準