1、認識SpringMVC
1、什么是MVC
MVC是一種軟體架構的思想,將軟體按照模型、視圖、控制器來劃分
M:Model,模型層,指工程中的JavaBean,作用是處理資料
JavaBean分為兩類:
- 一類稱為物體類Bean:專門存盤業務資料的,如 Student、User 等
- 一類稱為業務處理 Bean:指 Service 或 Dao 物件,專門用于處理業務邏輯和資料訪問,
V:View,視圖層,指工程中的html或jsp等頁面,作用是與用戶進行互動,展示資料
C:Controller,控制層,指工程中的servlet,作用是接收請求和回應瀏覽器
MVC的作業流程:
用戶通過視圖層發送請求到服務器,在服務器中請求被Controller接收,Controller呼叫相應的Model層處理請求,處理完畢將結果回傳到Controller,Controller再根據請求處理的結果找到相應的View視圖,渲染資料后最終回應給瀏覽器
2、什么是SpringMVC
SpringMVC是Spring的一個后續產品,是Spring的一個子專案
SpringMVC 是 Spring 為表述層開發提供的一整套完備的解決方案,在表述層框架歷經 Strust、WebWork、Strust2 等諸多產品的歷代更迭之后,目前業界普遍選擇了 SpringMVC 作為 Java EE 專案表述層開發的首選方案,
注:三層架構分為表述層(或表示層)、業務邏輯層、資料訪問層,表述層表示前臺頁面和后臺servlet
2、HelloSpringMVC
1、快速入門
匯入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.3</version>
</dependency>
添加web框架


在web.xml中配置servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--配置 / 則不能匹配jsp靜態資源的請求-->
<!--配置 /* 匹配所有請求-->
<!--此配置覆寫了tomcat中的默認servlet,但是jsp檔案有別的servlet匹配-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
關于<url-pattern>為什么要設定成/
tomcat提供的兩個Servlet
1、DefaultServlet
DefaultServlet為默認的Servlet,當客戶端請求不能匹配其他所有Servlet時,將由DefaultServlet處理,它配置的url-pattern為/,
DefaultServlet主要用于處理靜態資源,如HTML、圖片、CSS、JS檔案等,而且為了提升服務器性能,Tomcat對訪問檔案進行快取,按照默認配置,客戶端請求路徑與資源的物理路徑是一致的,
如果我們希望Web應用覆寫Tomcat的DefaultServlet配置,只需將“ / ”添加到自定義Servlet的url-pattern中即可(此時,自定義Servlet將成為Web應用的默認的Servlet),
2、JspServlet
默認情況下,JspServlet的url-pattern為.jsp和.jspx,因此他負責處理所有JSP檔案的請求,
JspServlet主要做了這些事情:
根據JSP檔案生成對應Servlet的Java代碼(JSP檔案生成類的父類為org.apache.jasper.runtime.HttpJspBase——實作了Servlet介面),
將Java代碼編譯為Java Class,Tomcat支持Ant和JDT(Eclipse提供的編譯器)兩種編譯JSP類,默認采用JDT,
構造Servlet類實體并且執行請求
DispatcherServlet的配置/和/*的區別
如果把DispatcherServlet的url-pattern配置成/*,那么它會覆寫掉jsp servlet,所有的jsp請求最交給DispatchServlet處理,如果Controller中沒有配置相關處理方法那么會無法處理,事實上沒有必要越俎代庖的處理.jsp請求,完全可以交給Tomcat容器處理jsp請求,因此DispatchServlet要配置成/,
配置SpringMVC組態檔
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--組件掃描-->
<context:component-scan base-package="com.zh.controller"/>
<!-- <mvc:annotation-driven /> 會自動注冊DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter 兩個bean,
是spring MVC為@Controllers分發請求所必須的,它提供了資料系結支持,讀取json的支持 -->
<mvc:annotation-driven/>
<!--訪問靜態資源時使用默認的處理器處理,若不配置,則所有請求都當做正常請求處理,會添加視圖決議器的前后綴,則會找不到資源-->
<mvc:default-servlet-handler/>
<!-- 配置jsp視圖決議器 -->
<bean id="jspViewResolver">
<property name="prefix" value="https://www.cnblogs.com/WEB-INF/jsp/"/>
<property name="suffix" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/.jsp"/>
</bean>
</beans>
控制器
@Controller
public class HelloController {
@RequestMapping("/hello")
public String Hello(){
return "hello";
}
}

環境搭建成功
總結:
瀏覽器發送請求,若請求地址符合前端控制器的url-pattern,該請求就會被前端控制器DispatcherServlet處理,前端控制器會讀取SpringMVC的核心組態檔,通過掃描組件找到控制器,將請求地址和控制器中@RequestMapping注解的value屬性值進行匹配,若匹配成功,該注解所標識的控制器方法就是處理請求的方法,處理請求的方法需要回傳一個字串型別的視圖名稱,該視圖名稱會被視圖決議器決議,加上前綴和后綴組成視圖的路徑,通過Thymeleaf對視圖進行渲染,最終轉發到視圖所對應頁面

3、@RequestMapping注解
1、@RequestMapping注解的功能
從注解名稱上我們可以看到,@RequestMapping注解的作用就是將請求和處理請求的控制器方法關聯起來,建立映射關系,
SpringMVC 接收到指定的請求,就會來找到在映射關系中對應的控制器方法來處理這個請求,
2、@RequestMapping注解的位置
@RequestMapping標識一個類:設定映射請求的請求路徑的初始資訊
@RequestMapping標識一個方法:設定映射請求請求路徑的具體資訊
@Controller
@RequestMapping("/test")
public class RequestMappingController {
//此時請求映射所映射的請求的請求路徑為:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}
}
3、@RequestMapping注解的value屬性
@RequestMapping注解的value屬性通過請求的請求地址匹配請求映射
@RequestMapping注解的value屬性是一個字串型別的陣列,表示該請求映射能夠匹配多個請求地址所對應的請求
@RequestMapping注解的value屬性必須設定,至少通過請求地址匹配請求映射
@Controller
public class Test {
@RequestMapping(value = https://www.cnblogs.com/Upzhh/archive/2022/12/23/{"/test01","/test02"})
public String test01(){
return "test";
}
}
4、@RequestMapping注解的method屬性
@RequestMapping注解的method屬性通過請求的請求方式(get或post)匹配請求映射
@RequestMapping注解的method屬性是一個RequestMethod型別的陣列,表示該請求映射能夠匹配多種請求方式的請求
若當前請求的請求地址滿足請求映射的value屬性,但是請求方式不滿足method屬性,則瀏覽器報錯405:Request method 'POST' not supported
@RequestMapping(
value = https://www.cnblogs.com/Upzhh/archive/2022/12/23/{"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
return "success";
}
注:
1、對于處理指定請求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
處理get請求的映射-->@GetMapping
處理post請求的映射-->@PostMapping
處理put請求的映射-->@PutMapping
處理delete請求的映射-->@DeleteMapping
2、常用的請求方式有get,post,put,delete
但是目前瀏覽器只支持get和post,若在form表單提交時,為method設定了其他請求方式的字串(put或delete),則按照默認的請求方式get處理
若要發送put和delete請求,則需要通過spring提供的過濾器HiddenHttpMethodFilter,在RESTful部分會講到
5、@RequestMapping注解的params屬性(了解)
@RequestMapping注解的params屬性通過請求的請求引數匹配請求映射
@RequestMapping注解的params屬性是一個字串型別的陣列,可以通過四種運算式設定請求引數和請求映射的匹配關系
"param":要求請求映射所匹配的請求必須攜帶param請求引數
"!param":要求請求映射所匹配的請求必須不能攜帶param請求引數
"param=value":要求請求映射所匹配的請求必須攜帶param請求引數且param=value
"param!=value":要求請求映射所匹配的請求必須攜帶param請求引數但是param!=value
注:
若當前請求滿足@RequestMapping注解的value和method屬性,但是不滿足params屬性,此時頁面回報錯400:Parameter conditions "username, password!=123456" not met for actual request parameters: username={admin}, password={123456}
6、@RequestMapping注解的headers屬性(了解)
@RequestMapping注解的headers屬性通過請求的請求頭資訊匹配請求映射
@RequestMapping注解的headers屬性是一個字串型別的陣列,可以通過四種運算式設定請求頭資訊和請求映射的匹配關系
"header":要求請求映射所匹配的請求必須攜帶header請求頭資訊
"!header":要求請求映射所匹配的請求必須不能攜帶header請求頭資訊
"header=value":要求請求映射所匹配的請求必須攜帶header請求頭資訊且header=value
"header!=value":要求請求映射所匹配的請求必須攜帶header請求頭資訊且header!=value
若當前請求滿足@RequestMapping注解的value和method屬性,但是不滿足headers屬性,此時頁面顯示404錯誤,即資源未找到
7、SpringMVC支持ant風格的路徑
?:表示任意的單個字符
*:表示任意的0個或多個字符
**:表示任意的一層或多層目錄
注意:在使用**時,只能使用/**/xxx的方式
8、SpringMVC支持路徑中的占位符(重點)
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
SpringMVC路徑中的占位符常用于RESTful風格中,當請求路徑中將某些資料通過路徑的方式傳輸到服務器中,就可以在相應的@RequestMapping注解的value屬性中通過占位符{xxx}表示傳輸的資料,在通過@PathVariable注解,將占位符所表示的資料賦值給控制器方法的形參
4、SpringMVC獲取請求引數
1、通過ServletAPI獲取
將HttpServletRequest作為控制器方法的形參,此時HttpServletRequest型別的引數表示封裝了當前請求的請求報文的物件
//1、通過ServletAPI獲取
@RequestMapping("/test01")
public String test01(HttpServletRequest request){
String name = request.getParameter("name");
String id = request.getParameter("id");
System.out.println("=====test01=====");
System.out.println(name);
System.out.println(id);
return "test";
}
2、通過控制器方法的形參獲取請求引數
在控制器方法的形參位置,設定和請求引數同名的形參,當瀏覽器發送請求,匹配到請求映射時,在DispatcherServlet中就會將請求引數賦值給相應的形參
//2、通過請求引數名字
@RequestMapping("/test02")
public String test02(String name,String id){
System.out.println("=====test02=====");
System.out.println(name);
System.out.println(id);
return "test";
}
注:
若請求所傳輸的請求引數中有多個同名的請求引數,此時可以在控制器方法的形參中設定字串陣列或者字串型別的形參接收此請求引數
若使用字串陣列型別的形參,此引數的陣列中包含了每一個資料
若使用字串型別的形參,此引數的值為每個資料中間使用逗號拼接的結果
作用:獲取前端多選框資料
3、@RequestParam
@RequestParam是將請求引數和控制器方法的形參創建映射關系
@RequestParam注解一共有三個屬性:
value:指定為形參賦值的請求引數的引數名
required:設定是否必須傳輸此請求引數,默認值為true
若設定為true時,則當前請求必須傳輸value所指定的請求引數,若沒有傳輸該請求引數,且沒有設定defaultValue屬性,則頁面報錯400:Required String parameter 'xxx' is not present;若設定為false,則當前請求不是必須傳輸value所指定的請求引數,若沒有傳輸,則注解所標識的形參的值為null
defaultValue:不管required屬性值為true或false,當value所指定的請求引數沒有傳輸或傳輸的值為""時,則使用默認值為形參賦值
//3、當前端引數名和方法形參名字不一樣時通過@RequestParam獲取
@RequestMapping("/test03")
public String test03(@RequestParam("name") String username, String id){
System.out.println("=====test03=====");
System.out.println(username);
System.out.println(id);
return "test";
}
4、@RequestHeader
@RequestHeader是將請求頭資訊和控制器方法的形參創建映射關系
@RequestHeader注解一共有三個屬性:value、required、defaultValue,用法同@RequestParam
//4、獲取請求頭
@RequestMapping("/test04")
public String test04(@RequestHeader("host") String host){
System.out.println("=====test04=====");
System.out.println(host);
return "test";
}
5、@CookieValue
@CookieValue是將cookie資料和控制器方法的形參創建映射關系
@CookieValue注解一共有三個屬性:value、required、defaultValue,用法同@RequestParam
//5、獲取cookie引數
@RequestMapping("/test05")
public String test05(@CookieValue("JSESSIONID") String session){
System.out.println("=====test05=====");
System.out.println(session);
return "test";
}
6、通過POJO獲取請求引數
可以在控制器方法的形參位置設定一個物體型別別的形參,此時若瀏覽器傳輸的請求引數的引數名和物體類中的屬性名一致,那么請求引數就會為此屬性賦值
<form action="/SpringMVC_03_Parameter/pojo" method="post">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
性別:<input type="radio" name="sex" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/男">男
<input type="radio" name="sex" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/女">女<br>
年齡:<input type="text" name="age"><br>
<input type="submit">
</form>
public class User {
private String username;
private String password;
private String sex;
private String age;
public User() {
}
......
//6、獲取屬性自動封裝到類中
@RequestMapping("/pojo")
public String test06(User user){
System.out.println("=====pojo=====");
System.out.println(user);
return "test";
}
User{username='張三', password='123321', sex='男', age='11'}
7、解決獲取請求引數的亂碼問題
配置過濾器
<!--配置springMVC的編碼過濾器-->
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!--這里設定成/*-->
<url-pattern>/*</url-pattern>
</filter-mapping>
注:
SpringMVC中處理編碼的過濾器一定要配置到其他過濾器之前,否則無效
5、域物件共享資料
1、使用ServletAPI向request域物件共享資料
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope", "hello,servletAPI");
return "success";
}
2、使用ModelAndView向request域物件共享資料
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向請求域共享資料
* View主要用于設定視圖,實作頁面跳轉
*/
ModelAndView mav = new ModelAndView();
//向請求域共享資料
mav.addObject("testScope", "hello,ModelAndView");
//設定視圖,實作頁面跳轉
mav.setViewName("success");
return mav;
}
3、使用Model向request域物件共享資料
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testScope", "hello,Model");
return "success";
}
4、使用map向request域物件共享資料
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testScope", "hello,Map");
return "success";
}
5、使用ModelMap向request域物件共享資料
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testScope", "hello,ModelMap");
return "success";
}
6、Model、ModelMap、Map的關系
Model、ModelMap、Map型別的引數其實本質上都是 BindingAwareModelMap 型別的
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
7、向session域共享資料
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}
8、向application域共享資料
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}
9、回顧四大域物件
【1】ServletContext 域--- application
ServletContext代表整個web應用的物件,
生命周期:web應用被tomcat服務器加載時,ServletContext物件產生,生命周期開始,
web應用被移除容器或者tomcat服務器關閉的時候,ServletContext物件銷毀,生命周期結束,
作用范圍:整個web應用,
主要功能:在整個web應用范圍內共享資料,
【2】session 域---session
Session代表整個會話的物件
生命周期:當呼叫request.getSession()時,Session物件被創建,生命周期開始
呼叫session.invalidate()方法銷毀Session物件
在設定的時間內,Session物件沒有被使用,則Session物件被銷毀,默認為30分鐘
當服務器意外關閉的時候,Session物件被銷毀,當服務器正常關閉的時候,Session物件中仍有資料,會序列化到磁盤上形成一個檔案,
這個程序稱之為鈍化,在服務器再次啟動的時候,這個檔案會被重新讀取到服務器中使用,這個程序稱之為活化,
作用范圍:整個會話范圍
主要功能:在會話范圍內共享資料
【3】request 域---request
Request代表請求的物件
生命周期:請求鏈開始,request物件被創建,請求鏈結束,request物件銷毀,
作用范圍:整個請求鏈
主要功能:在請求鏈內共享資料
【4】pageContext域---pageContext
PageContext代表當前頁面的物件
生命周期:在訪問jsp頁面時,pageContext物件產生,生命周期開始,在結束訪問jsp頁面時,pageContext物件銷毀,生命周期結束,
作用范圍:整個jsp頁面
主要功能:在整個jsp頁面內共享資料
6、HttpMessageConverter
HttpMessageConverter,報文資訊轉換器,將請求報文轉換為Java物件,或將Java物件轉換為回應報文
HttpMessageConverter提供了兩個注解和兩個型別:@RequestBody,@ResponseBody,RequestEntity,
ResponseEntity
1、@RequestBody
@RequestBody可以獲取請求體,需要在控制器方法設定一個形參,使用@RequestBody進行標識,當前請求的請求體就會為當前注解所標識的形參賦值
<form action="/SpringMVC_03_Parameter/pojo" method="post">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
性別:<input type="radio" name="sex" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/男">男
<input type="radio" name="sex" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/女">女<br>
年齡:<input type="text" name="age"><br>
<input type="submit">
</form>
public String test06(User user,@RequestBody String body){
System.out.println("=====pojo=====");
System.out.println(body);
System.out.println(user);
return null;
}
輸出結果:
username=%C3%A5%C2%BC%C2%A0%C3%A4%C2%B8%C2%89&password=111&sex=%C3%A7%C2%94%C2%B7&age=11
User{username='??????', password='111', sex='??·', age='11'}
2、RequestEntity
RequestEntity封裝請求報文的一種型別,需要在控制器方法的形參中設定該型別的形參,當前請求的請求報文就會賦值給該形參,可以通過getHeaders()獲取請求頭資訊,通過getBody()獲取請求體資訊
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}
輸出結果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"", sec-ch-ua-mobile:"?0", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"]
requestBody:username=admin&password=123
3、@ResponseBody
@ResponseBody用于標識一個控制器方法,可以將該方法的回傳值直接作為回應報文的回應體回應到瀏覽器
@RequestMapping("/pojo")
@ResponseBody
public String test06(User user){
System.out.println("=====pojo=====");
System.out.println(user);
return user.toString();
}
結果:瀏覽器頁面顯示User{username='?? ??‰', password='132', sex='?”·', age='11'}
控制臺輸出
pojo
User{username='??????', password='132', sex='??·', age='11'}
4、解決亂碼問題
亂碼問題我認為可以分為兩種
1、獲取前端引數亂碼
解決方法:配置過濾器
<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></filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
過濾所有請求:/*
配置好后,控制臺輸出:
pojo
User{username='張三', password='123321', sex='男', age='111'}
2、后端回傳資料后前端顯示亂碼
打開前端控制臺可以發現回應報文有如下資訊:
Content-Type:text/html;charset=ISO-8859-1
這就是導致亂碼的原因
為什么回傳的資料編碼是ISO-8859-1?
在spring處理ResponseBody時涉及到org.springframework.http.converter.StringHttpMessageConverter這個類,該類在默認實作中將defaultCharset設為ISO-8859-1,當@RequestMapping標記的方法未配置produces屬性時,將自動使用默認編碼;如果配置了produces屬性,AbstractHttpMessageConverter中的write方法將不會受supportedMediaTypes影響,而用produce設定的header賦值給contentType,

原始碼默認為ISO-8859-1
因此,解決方法是:指定給訊息轉換器的編碼為utf-8
解決方法:
1、配置@RequestMapping(value = "https://www.cnblogs.com/pojo",produces = "text/html;charset=UTF-8")
2、配置 HttpMessageConverter
<mvc:annotation-driven>
<mvc:message-converters>
<bean >
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
這里值設定了回傳一種媒體型別,可以設定多種,SpringMVC會自動尋找合適的訊息轉換器
關于過濾器為什么無法設定ContentType
@RequestMapping(value = "https://www.cnblogs.com/rulelist", method = RequestMethod.GET)
@ResponseBody
public String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader("test", "test");
return service.getRuleList();
}
通過驗證,我們可以看到test項已經被成功添加到response的頭部資訊
`Content-Length: 2 kilobytes`
`Content-Type: text/plain;charset=ISO-8859-1`
`Server: Apache-Coyote/1.1`
`test: test`
@RequestMapping(value = "https://www.cnblogs.com/rulelist", method = RequestMethod.GET)
@ResponseBody
public String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader("Content-Type", "application/json;charset=UTF-8");
return service.getRuleList();
}
沒有設定成功
`Content-Length: 2 kilobytes`
`Content-Type: text/plain;charset=ISO-8859-1`
`Server: Apache-Coyote/1.1`
下圖清晰地向大家展示了Spring MVC處理HTTP請求的流程,(圖片來自網路)

具體流程如下:
1、DispatcherServlet接收到Request請求
2、HandlerMapping選擇一個合適的Handler處理Request請求
3-4、 選擇合適的HandlerAdapter,呼叫用戶撰寫的Controller處理業務邏輯,(HandlerAdapter主要是幫助Spring MVC支持多種型別的Controller)
5、Controller將回傳結果放置到Model中并且回傳view名稱給Handler Adapter
6、DispatcherServlet選擇合適的ViewResolver來生成View物件
7-8、 View物件利用Model中的資料進行渲染并回傳資料

從上面的流程圖我們可以看到,content-type header是單獨被處理的,media-type被單獨的邏輯進行處理,因此直接在ServletResponse中設定content-type header并不能正常生效,
5、SpringMVC處理json
@ResponseBody處理json的步驟:
a>匯入jackson的依賴
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
b>在SpringMVC的核心組態檔中開啟mvc的注解驅動,此時在HandlerAdaptor中會自動裝配一個訊息轉換器:MappingJackson2HttpMessageConverter,可以將回應到瀏覽器的Java物件轉換為Json格式的字串
<mvc:annotation-driven />
c>在處理器方法上使用@ResponseBody注解進行標識
d>將Java物件直接作為控制器方法的回傳值回傳,就會自動轉換為Json格式的字串
@RequestMapping("/pojo")
@ResponseBody
public User test06(User user){
System.out.println("=====pojo=====");
System.out.println(user);
return user;
}
瀏覽器的頁面中展示的結果:
{"username":"張三","password":"123321","sex":"男","age":"11"}
6、@RestController注解
@RestController注解是springMVC提供的一個復合注解,標識在控制器的類上,就相當于為類添加了@Controller注解,并且為其中的每個方法添加了@ResponseBody注解
7、ResponseEntity
ResponseEntity用于控制器方法的回傳值型別,該控制器方法的回傳值就是回應到瀏覽器的回應報文
7、SpringMVC的視圖
SpringMVC中的視圖是View介面,視圖的作用渲染資料,將模型Model中的資料展示給用戶
SpringMVC視圖的種類很多,默認有轉發視圖和重定向視圖
當工程引入jstl的依賴,轉發視圖會自動轉換為JstlView
若使用的視圖技術為Thymeleaf,在SpringMVC的組態檔中配置了Thymeleaf的視圖決議器,由此視圖決議器決議之后所得到的是ThymeleafView
1、ThymeleafView
當控制器方法中所設定的視圖名稱沒有任何前綴時,此時的視圖名稱會被SpringMVC組態檔中所配置的視圖決議器決議,視圖名稱拼接視圖前綴和視圖后綴所得到的最終路徑,會通過轉發的方式實作跳轉
@RequestMapping("/testHello")
public String testHello(){
return "hello";
}
2、轉發視圖
SpringMVC中默認的轉發視圖是InternalResourceView
SpringMVC中創建轉發視圖的情況:
當控制器方法中所設定的視圖名稱以"forward:"為前綴時,創建InternalResourceView視圖,此時的視圖名稱不會被SpringMVC組態檔中所配置的視圖決議器決議,而是會將前綴"forward:"去掉,剩余部分作為最終路徑通過轉發的方式實作跳轉
例如"forward:/","forward:/employee"
@RequestMapping("/testForward")
public String testForward(){
return "forward:/testHello";
}
實體
@RequestMapping("/test07")
public String test07(){
System.out.println("進入Test07");
return "forward:/test08";
}
@RequestMapping("/test08")
public ModelAndView test08(User user){
ModelAndView modelAndView = new ModelAndView();
System.out.println("進入Test08");
modelAndView.addObject("user",user.toString());
modelAndView.setViewName("user");
return modelAndView;
}
3、重定向視圖
SpringMVC中默認的重定向視圖是RedirectView
當控制器方法中所設定的視圖名稱以"redirect:"為前綴時,創建RedirectView視圖,此時的視圖名稱不會被SpringMVC組態檔中所配置的視圖決議器決議,而是會將前綴"redirect:"去掉,剩余部分作為最終路徑通過重定向的方式實作跳轉
例如"redirect:/","redirect:/employee"
@RequestMapping("/testRedirect")
public String testRedirect(){
return "redirect:/testHello";
}
注:
重定向視圖在決議時,會先將redirect:前綴去掉,然后會判斷剩余部分是否以/開頭,若是則會自動拼接背景關系路徑
4、視圖控制器view-controller
當控制器方法中,僅僅用來實作頁面跳轉,即只需要設定視圖名稱時,可以將處理器方法使用view-controller標簽進行表示
<!--
path:設定處理的請求地址
view-name:設定請求地址所對應的視圖名稱
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
注:
當SpringMVC中設定任何一個view-controller時,其他控制器中的請求映射將全部失效,此時需要在SpringMVC的核心組態檔中設定開啟mvc注解驅動的標簽:
<mvc:annotation-driven />
8、RESTful
1、RESTful簡介
REST:Representational State Transfer,表現層資源狀態轉移,
a>資源
資源是一種看待服務器的方式,即,將服務器看作是由很多離散的資源組成,每個資源是服務器上一個可命名的抽象概念,因為資源是一個抽象的概念,所以它不僅僅能代表服務器檔案系統中的一個檔案、資料庫中的一張表等等具體的東西,可以將資源設計的要多抽象有多抽象,只要想象力允許而且客戶端應用開發者能夠理解,與面向物件設計類似,資源是以名詞為核心來組織的,首先關注的是名詞,一個資源可以由一個或多個URI來標識,URI既是資源的名稱,也是資源在Web上的地址,對某個資源感興趣的客戶端應用,可以通過資源的URI與其進行互動,
b>資源的表述
資源的表述是一段對于資源在某個特定時刻的狀態的描述,可以在客戶端-服務器端之間轉移(交換),資源的表述可以有多種格式,例如HTML/XML/JSON/純文本/圖片/視頻/音頻等等,資源的表述格式可以通過協商機制來確定,請求-回應方向的表述通常使用不同的格式,
c>狀態轉移
狀態轉移說的是:在客戶端和服務器端之間轉移(transfer)代表資源狀態的表述,通過轉移和操作資源的表述,來間接實作操作資源的目的,
2、RESTful的實作
具體說,就是 HTTP 協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE,
它們分別對應四種基本操作:GET 用來獲取資源,POST 用來新建資源,PUT 用來更新資源,DELETE 用來洗掉資源,
REST 風格提倡 URL 地址使用統一的風格設計,從前到后各個單詞使用斜杠分開,不使用問號鍵值對方式攜帶請求引數,而是將要發送給服務器的資料作為 URL 地址的一部分,以保證整體風格的一致性,
| 操作 | 傳統方式 | REST風格 |
|---|---|---|
| 查詢操作 | getUserById?id=1 | user/1-->get請求方式 |
| 保存操作 | saveUser | user-->post請求方式 |
| 洗掉操作 | deleteUser?id=1 | user/1-->delete請求方式 |
| 更新操作 | updateUser | user-->put請求方式 |
3、HiddenHttpMethodFilter
由于瀏覽器只支持發送get和post方式的請求,那么該如何發送put和delete請求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 幫助我們將 POST 請求轉換為 DELETE 或 PUT 請求
HiddenHttpMethodFilter 處理put和delete請求的條件:
a>當前請求的請求方式必須為post
b>當前請求必須傳輸請求引數_method
滿足以上條件,HiddenHttpMethodFilter 過濾器就會將當前請求的請求方式轉換為請求引數_method的值,因此請求引數_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>
注:
目前為止,SpringMVC中提供了兩個過濾器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注冊時,必須先注冊CharacterEncodingFilter,再注冊HiddenHttpMethodFilter
原因:
在 CharacterEncodingFilter 中通過 request.setCharacterEncoding(encoding) 方法設定字符集的
request.setCharacterEncoding(encoding) 方法要求前面不能有任何獲取請求引數的操作
而 HiddenHttpMethodFilter 恰恰有一個獲取請求方式的操作:
String paramValue = https://www.cnblogs.com/Upzhh/archive/2022/12/23/request.getParameter(this.methodParam);
4、RESTful案例
準備作業
配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.zh"/>
<mvc:default-servlet-handler/>
<bean id="jspViewResolver">
<property name="prefix" value="https://www.cnblogs.com/WEB-INF/jsp/"/>
<property name="suffix" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/.jsp"/>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean >
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:view-controller path="/" view-name="index"/>
<mvc:view-controller path="/UserList" view-name="userlist"/>
<mvc:view-controller path="/addUser" view-name="addUser"/>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<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>
<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>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
User類
package com.zh.pojo;
import java.util.Objects;
public class User {
private int id;
private String name;
private int age;
//省略set和get....
}
DAO類
package com.zh.userDAO;
import com.zh.pojo.User;
import java.util.ArrayList;
import java.util.List;
public class UserDAO {
private List<User> userList = new ArrayList<>();
private int idNum = 4;
public UserDAO() {
userList.add(new User(1,"張三",12));
userList.add(new User(2,"李四",14));
userList.add(new User(3,"王五",16));
userList.add(new User(4,"趙六",17));
}
//查詢所有用戶
public List<User> getUserList(){
return userList;
}
//根據用戶id查詢用戶
public User getUserById(int id){
for (User user : userList) {
if (user.getId() == id){
return user;
}
}
return null;
}
//添加用戶
public boolean addUser(User user){
idNum++;
user.setId(idNum);
return userList.add(user);
}
//根據id洗掉用戶
public boolean delUser(int id){
for (User user : userList) {
if (user.getId() == id){
return userList.remove(user);
}
}
return false;
}
//根據id修改用戶
public boolean updateUser(int id,User newUser){
for (User user : userList) {
if (user.getId() == id){
user.setAge(newUser.getAge());
user.setName(newUser.getName());
return true; }
}
return false;
}
}
Controller類
package com.zh.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zh.pojo.User;
import com.zh.userDAO.UserDAO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* RESRFul風格實體
*/
@Controller
public class Controller01 {
private UserDAO userDAO = new UserDAO();
/**
* 使用RESTFul模擬操作用戶資訊
* /user get 查詢所有用戶資訊
* /user/1 get 查詢指定用戶資訊
* /user post 增加用戶資訊
* /user/1 delete 洗掉指定用戶資訊
* /user/1 put 修改指定用戶資訊
*/
//查詢所有用戶,回傳JSON陣列
@RequestMapping(value = "https://www.cnblogs.com/user",method = RequestMethod.GET)
@ResponseBody
public String getUser() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String userListString = objectMapper.writeValueAsString(userDAO.getUserList());
return userListString;
}
//查詢一個用戶,并將用戶資料系結到user.jsp頁面上并進行跳轉
@RequestMapping(value = "https://www.cnblogs.com/user/{id}",method = RequestMethod.GET)
public String getUserById(@PathVariable("id") int id, Model model){
User user = userDAO.getUserById(id);
model.addAttribute("user",user);
return "user";
}
//增加一個用戶,然后重定向到串列頁面
@RequestMapping(value = "https://www.cnblogs.com/user",method = RequestMethod.POST)
public String addUser(String username,int age){
userDAO.addUser(new User(username,age));
return "redirect:/UserList";
}
//洗掉一個用戶
@RequestMapping(value = "https://www.cnblogs.com/user",method = RequestMethod.DELETE)
public String delUser(int id){
userDAO.delUser(id);
return "redirect:/UserList";
}
//修改用戶資訊
@RequestMapping(value = "https://www.cnblogs.com/user",method = RequestMethod.PUT)
@ResponseBody
public void updUser(int id,String username,int age){
userDAO.updateUser(id,new User(username,age));
}
}
JSP頁面
UserList
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>UserList</title>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<style type="text/css">
table{
margin-top: 200px;
}
td{
text-align: center;
}
a{
text-decoration: none;
}
</style>
</head>
<body>
<table border="1" cellspacing="0" align="center" id="table">
</table> <p align="center">
<input type="button" id="btn1" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/重繪">
<input type="button" id="btn2" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/增加">
</p>
</body>
<script>
function getPath(){
let pathName = document.location.pathname;
let index = pathName.substr(1).indexOf("/");
let result = pathName.substr(0,index+1);
return result;
}
function aDelClick(){
var as = document.getElementsByClassName('del');
for (let i = 0; i < as.length; i++) {
as[i].onclick = function (){
let tr = this.parentNode.parentNode;
let id = tr.children[0].innerHTML;
let name = tr.children[1].innerHTML;
var flag = confirm("確認洗掉"+ name + "嗎?");
if(flag){
//洗掉tr對應用戶的資訊
$.ajax({
type: 'post',
dateType: 'json',
url:getPath()+"/user",
data: {
_method:'delete',
id:id
},
success:function (){
}
})
tr.parentNode.removeChild(tr);
}
}
}
}
function aUpdClick(){
var as = document.getElementsByClassName('upd');
for (let i = 0; i < as.length; i++) {
as[i].onclick = function (){
let tr = this.parentNode.parentNode;
let id = tr.children[0].innerHTML;
window.location = getPath()+'/user'+'/'+id;
}
}
}
function getUserList(){
var table = $("table");
$.ajax({
type:"get",
dateType:"json",
url:getPath()+"/user",
data:{},
success:function (msg){
table.text('');
table.append('<tr><th colspan="4">用戶串列</th></tr><tr><td>id</td><td>姓名</td> <td>年齡</td><td>操作</td> </tr>')
for (let i = 0; i <msg.length; i++) {
let tr = '<tr><td>'+msg[i].id+'</td><td>'+msg[i].name+'</td><td>'+msg[i].age+'</td><td><a href="https://www.cnblogs.com/Upzhh/archive/2022/12/23/#" >修改</a> <a href="https://www.cnblogs.com/Upzhh/archive/2022/12/23/#" >洗掉</a></tr>';
table.append(tr);
}
aDelClick();
aUpdClick();
}
})
}
$("#btn1").click(function (){
getUserList();
})
$("#btn2").click(function (){
window.location = getPath()+'/addUser';
})
$(function (){
getUserList();
})
</script>
</html>
user.jsp
<%@ page import="com.zh.pojo.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<style type="text/css">
h1{
text-align: center;
}
#btn{
margin: 0 auto;
margin-top: 10px;
margin-left: 100px;
}
#out{
margin: 0 auto;
margin-left: 650px;
}
</style>
<body>
<div>
<h1>修改用戶</h1>
<div id="out">
<form action="/RESTFul/user" method="post">
<%
User user = (User) request.getAttribute("user");
%>
姓名:<input type="text" name="username" id="username" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/"><br>
年齡:<input type="text" name="age" id="age" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/"><br>
<div> <button type="button" id="btn1">修改</button>
<button type="button" id="btn2">回傳</button>
</div> </form> </div></div>
</body>
<script>
function getPath(){
let pathName = document.location.pathname;
let index = pathName.substr(1).indexOf("/");
let result = pathName.substr(0,index+1);
return result;
}
$('#btn1').click(function (){
var id = <%=user.getId()%>;
var username = $('#username').val();
var age = $('#age').val();
$.ajax({
type:'post',
dataType:'text',
url:getPath()+'/user',
data:{
_method:'put',
id:id,
username:username,
age:age
},
success:function (){
window.location = getPath()+'/UserList';
}
})
})
$('#btn2').click(function (){
window.location = getPath()+'/UserList'
})
</script>
</html>
adduser.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<style type="text/css">
h1{
text-align: center;
}
#submit{
margin: 0 auto;
margin-top: 10px;
margin-left: 100px;
}
#out{
margin: 0 auto;
margin-left: 650px;
}
</style>
<body>
<div>
<h1>增加用戶</h1>
<div id="out">
<form action="/RESTFul/user" method="post">
姓名:<input type="text" name="username"><br>
年齡:<input type="text" name="age"><br>
<div> <input type="submit" align="center" id="submit">
</div> </form> </div></div>
</body>
</html>
index
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<h4><a href="https://www.cnblogs.com/RESTFul/UserList">用戶串列</a></h4>
</body>
</html>
5、功能說明
查詢所有用戶
控制器映射的url是/user,請求方式是get,回傳dao類中的集合的json字串,前端頁面一開始就發送一次Ajax請求,收到資料之后執行回呼方法,根據JSON陣列的長度動態的添加進表格(拼接標簽),并為每個操作的a標簽添加一個點擊事件(點擊修改之后,js代碼獲取點擊時這一用戶的id,然后重定向到此url,例如/user/1,點擊洗掉之后,獲取到點擊時這一用戶的id,發送Ajax請求到服務器,請求洗掉用戶,然后重定向到串列頁面重繪)
查詢一個用戶
url是/user/{id},get請求,符合的url將會執行這個方法,控制器拿到id之后,在dao層中獲取到此用戶的資訊,然后把用戶資訊添加到請求域中,然后回傳視圖,前端中可以獲取到這個資料
增加用戶
點擊增加用戶之后,會跳轉到增加用戶的界面,url是/addUser,點擊修改之后,發送Ajax請求,url是/user,請求方式:post,控制器接收到資訊之后會操作資料,然后回傳操作狀態,前端根據回傳的結果提示資訊和進行跳轉回用戶串列
洗掉用戶
點擊洗掉按鈕之后,會發送一個Ajax請求,請求引數包含_method和id,請求url: /user,_methon值為delet,控制器收到資料執行洗掉用戶操作,洗掉完成前端進行跳轉會用戶串列
修改用戶
點擊修改用戶之后,會重定向到要修改的用戶的界面,例如/user/1,控制器已經將引數攜帶進請求中,可以進行回顯,點擊修改之后,提交用戶資訊,前端控制重定向回串列頁面
至此,完成了RESTful風格的增刪改查
| url | 操作 | 請求方式 |
|---|---|---|
| /user | 查詢所有用戶 | get |
| /user/1 | 查詢一個用戶 | get |
| /user | 增加用戶 | post |
| /user | 洗掉用戶 | delete |
| /user | 修改用戶 | put |
9、SpringMVC攔截器
請求---->過濾器--->前端控制器--->控制器
SpringMVC中的攔截器用于攔截控制器方法的執行
SpringMVC中的攔截器需要實作HandlerInterceptor
SpringMVC的攔截器必須在SpringMVC的組態檔中進行配置:
package net.biancheng.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class TestInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion方法在控制器的處理請求方法執行完成后執行,即視圖渲染結束之后執行");
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法在控制器的處理請求方法呼叫之后,決議視圖之前執行");
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法在控制器的處理請求方法呼叫之前執行");
return false;
}
}
上述攔截器的定義中實作了 HandlerInterceptor 介面,并實作了介面中的 3 個方法,說明如下,
- preHandle( ):該方法在控制器的處理請求方法前執行,其回傳值表示是否中斷后續操作,回傳 true 表示繼續向下執行,回傳 false 表示中斷后續操作,
- postHandle( ):該方法在控制器的處理請求方法呼叫之后、決議視圖之前執行,可以通過此方法對請求域中的模型和視圖做進一步的修改,
- afterCompletion( ):該方法在控制器的處理請求方法執行完成后執行,即視圖渲染結束后執行,可以通過此方法實作一些資源清理、記錄日志資訊等作業,
1、攔截器的配置
讓自定義的攔截器生效需要在 Spring MVC 的組態檔中進行配置,配置示例代碼如下:
<!-- 配置攔截器 -->
<mvc:interceptors>
<!-- 配置一個全域攔截器,攔截所有請求 -->
<bean />
<mvc:interceptor>
<!-- 配置攔截器作用的路徑 -->
<mvc:mapping path="/**" />
<!-- 配置不需要攔截作用的路徑 -->
<mvc:exclude-mapping path="" />
<!-- 定義<mvc:interceptor>元素中,表示匹配指定路徑的請求才進行攔截 -->
<bean />
</mvc:interceptor>
<mvc:interceptor>
<!-- 配置攔截器作用的路徑 -->
<mvc:mapping path="/gotoTest" />
<!-- 定義在<mvc:interceptor>元素中,表示匹配指定路徑的請求才進行攔截 -->
<bean />
</mvc:interceptor>
</mvc:interceptors>
在上述示例代碼中,元素說明如下,
- <mvc:interceptors>:該元素用于配置一組攔截器,
- <bean>:該元素是 mvc:interceptors 的子元素,用于定義全域攔截器,即攔截所有的請求,
- mvc:interceptor:該元素用于定義指定路徑的攔截器,
- mvc:mapping:該元素是 mvc:interceptor 的子元素,用于配置攔截器作用的路徑,該路徑在其屬性 path 中定義,path 的屬性值為
/**時,表示攔截所有路徑,值為/gotoTest時,表示攔截所有以/gotoTest結尾的路徑,如果在請求路徑中包含不需要攔截的內容,可以通過 mvc:exclude-mapping 子元素進行配置,
需要注意的是,mvc:interceptor 元素的子元素必須按照 mvc:mapping.../、mvc:exclude-mapping.../、<bean.../> 的順序配置,
對于路徑:
/**的意思是所有檔案夾及里面的子檔案夾
/*是所有檔案夾,不含子檔案夾
/是web專案的根目錄
2、示例
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/user"/>
<mvc:exclude-mapping path="/UserList"/>
<mvc:exclude-mapping path="/"/>
<bean />
</mvc:interceptor>
</mvc:interceptors>
配置了這個攔截器之后,對訪問UserList頁面和獲取User資料的請求以及首頁都不攔截
public class MyInterceptor implements HandlerInterceptor {
private Date date;
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("控制器執行前");
System.out.println(simpleDateFormat.format(new Date())+request.getRequestURI());
return true; }
}
當有多個攔截器時,順序和配置的順序有關
<mvc:interceptors>
<bean />
<bean />
</mvc:interceptors>
preHandle:攔截器--->1
preHandle:攔截器--->2
postHandle:攔截器--->2
postHandle:攔截器--->1
afterCompletion:攔截器--->2
afterCompletion:攔截器--->1
當第一個攔截器攔截時
preHandle:攔截器--->1
當后面的攔截器攔截請求時
preHandle:攔截器--->1
preHandle:攔截器--->2
afterCompletion:攔截器--->1
10、例外處理器
SpringMVC提供了一個處理控制器方法執行程序中所出現的例外的介面:HandlerExceptionResolver,該介面的類圖如下

SpringMVC中,處理例外通常有4種方式
@ExceptionHandler 注解
使用 @ExceptionHandler 注解實作例外處理
使用該注解有一個不好的地方就是:進行例外處理的方法必須與出錯的方法在同一個Controller里面,使用如下:
@Controller
public class GlobalController {
/**
* 用于處理例外的
* @return
*/
@ExceptionHandler({MyException.class})
public String exception(MyException e) {
System.out.println(e.getMessage());
e.printStackTrace();
return "exception";
}
@RequestMapping("test")
public void test() {
throw new MyException("出錯了!");
}
}
@ControllerAdvice+@ExceptionHandler
使用 @ControllerAdvice+@ExceptionHandler處理全域例外
@ExceptionHandler只能和發生例外的控制器在一起才能使用,也就是只能處理該控制器內的方法所拋出的例外,SpringMVC 3.2 新特性@ControllerAdvice是控制器增強,也就是專門處理例外的控制器,也就可以實作全域的例外處理,使用方法:
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(Exception.class)
public String ExceptionHandle(Exception ex, Model model){
model.addAttribute("ex",ex);
return "error";
}
}
例外處理介面 HandlerExceptionResolver
實作 Spring 的例外處理介面 HandlerExceptionResolver,自定義自己的例外處理器,
public class MyExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof ArithmeticException){
ModelAndView arithmeticException = new ModelAndView();
arithmeticException.setViewName("ArithmeticException");
return arithmeticException;
}else if(ex instanceof NullPointerException){
ModelAndView nullPointerException = new ModelAndView();
nullPointerException.setViewName("NullPointerException");
return nullPointerException;
}else {
ModelAndView error = new ModelAndView();
error.setViewName("error");
return error;
}
}
}
然后到SpringMVC組態檔中注冊這個bean
<bean />
可以看到原始碼中的例外處理器中已經有了我們自己定義的例外處理器

然后遍歷這些例外處理器,例外處理器處理完成之后會回傳視圖,如果視圖為空,則證明這個例外處理器無法處理這個例外,繼續遍歷,找到可以處理這個例外的例外處理器之后,回傳結果,進行視圖決議

SimpleMappingExceptionResolver
使用 Spring MVC 提供的簡單例外處理器 SimpleMappingExceptionResolver,
全域例外處理可使用 SimpleMappingExceptionResolver 來實作,它將例外類名映射為視圖名,即發生例外時使用對應的視圖報告例外,
使用方法,直接在組態檔注冊bean就行
<bean >
<!--例外沒有被特殊處理時,默認的視圖名字-->
<property name="defaultErrorView" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/error"/>
<!--例外資訊的引數名字,可以根據這個取出例外資訊-->
<property name="exceptionAttribute" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/ex"/>
<!-- 定義需要特殊處理的例外,用類名或完全路徑名作為key,例外頁名作為值 -->
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArithmeticException">ArithmeticException</prop>
<prop key="java.lang.NullPointerException">NullPointerException</prop>
</props>
</property>
</bean>
可以看到,注冊的這個例外處理器也被加載進來了

11、關于檔案的上傳和下載
1、檔案上傳
檔案上傳需要用到org.springframework.web.multipart.commons.CommonsMultipartResolver這個類,這個類極大簡便了我們對檔案操作
檔案上傳是專案開發中最常見的功能,為了能上傳檔案,必須將表單的method設定為POST,并將enctype設定為multipart/form-data,一旦設定了enctype為multipart/form-data,瀏覽器即會采用二進制流的方式來處理表單資料,只有在這樣的情況下,瀏覽器才會把用戶選擇的檔案以二進制資料發送給服務器,
Spring MVC 為檔案上傳提供了直接的支持,這種支持是用即插即用的 MultipartResolver 實作的,Spring MVC 使用 Apache Commons FileUpload技術實作了一個 MultipartResolver 實作類:CommonsMultipartResolver ,因此,SpringMVC的檔案上傳還需要依賴Apache Commons FileUpload的組件,
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
若沒有這匯入依賴,則報錯
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'multipartResolver':
Failed to introspect bean class [org.springframework.web.multipart.commons.CommonsMultipartResolver] for lookup method metadata:
could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory
SpringMVC背景關系中默認沒有裝配 MultipartResolver,因此默認情況下其不能處理檔案上傳作業,如果想使用Spring的檔案上傳功能,則需要在背景關系中配置 MultipartResolver,
MultipartResolver 用于處理檔案上傳,當收到請求時 DispatcherServlet 的 checkMultipart() 方法會呼叫 MultipartResolver 的 isMultipart() 方法判斷請求中是否包含檔案,如果請求資料中包含檔案,則呼叫 MultipartResolver 的 resolveMultipart() 方法對請求的資料進行決議,然后將檔案資料決議成 MultipartFile 并封裝在 MultipartHttpServletRequest (繼承了 HttpServletRequest) 物件中,最后傳遞給 Controller
<!--組態檔上傳相關-->
2 <bean id="multipartResolver">
3 <!-- 設定檔案上傳的最大值-->
4 <property name="maxUploadSize" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/10485760"></property>
5 <!-- 設定檔案上傳時寫入記憶體的最大值,如果小于這個引數不會生成臨時檔案,默認為10240 -->
6 <property name="maxInMemorySize" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/4096"></property>
7 <!-- 設定默認編碼 -->
8 <property name="defaultEncoding" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/UTF-8"></property>
9 </bean>
這個類還有很多引數設定,根據需求設定
上傳檔案的頁面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/RESTFul/upload" method="post" enctype="multipart/form-data">
檔案說明:<input type="text" name="wjsm" /><br/>
附件:<input type="file" name="file" /><br/>
<input type="submit" value="https://www.cnblogs.com/Upzhh/archive/2022/12/23/提交"/>
</form>
</body>
</html>
撰寫Controller以及處理方法:測驗ok
MultipartFile 封裝了請求資料中的檔案,此時這個檔案存盤在記憶體中或臨時的磁盤檔案中,需要將其轉存到一個合適的位置,因為請求結束后臨時存盤將被清空,在 MultipartFile 介面中有如下方法:
- String getName(); // 獲取引數的名稱
- String getOriginalFilename(); // 獲取檔案的原名稱
- String getContentType(); // 檔案內容的型別
- boolean isEmpty(); // 檔案是否為空
- long getSize(); // 檔案大小
- byte[] getBytes(); // 將檔案內容以位元組陣列的形式回傳
- InputStream getInputStream(); // 將檔案內容以輸入流的形式回傳
- void transferTo(File dest); // 將檔案內容傳輸到指定檔案中
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws IOException {
//獲取存盤該檔案的檔案夾在服務器上的真實路徑,會自動拼接上
String realPath = request.getSession().getServletContext().getRealPath("/resource/userdata");
//獲取檔案原來的名字
String fileName = file.getOriginalFilename();
//創建一個File,此file會自動拼接路勁,就是這個檔案下載之后存盤到服務器上的路徑
File desFile = new File(realPath,fileName);
//這個檔案如果為空,則創建一個
if (!desFile.exists()){
desFile.mkdirs();
}
//開始往這個檔案里面寫資料(復制)
file.transferTo(desFile);
return "success";
}
2、檔案下載
SpringMVC提供了一個 ResponseEntity 型別,使用它可以很方便地定義回傳的 HttpHeaders 和HttpStatus ,
@RequestMapping("/filedownload/{fileName}")
public ResponseEntity<byte[]> fileDownload(@PathVariable String fileName, HttpSession session) throws IOException {
//獲取檔案的真實路徑
String path = session.getServletContext().getRealPath("/resource/userdata");
File file = new File(path,fileName);
//創建一個對該檔案的輸入流
InputStream inputStream = new FileInputStream(file);
//創建一個和檔案大小一樣的byte陣列
byte[] bytes = new byte[inputStream.available()];
//把資料讀取到該陣列
inputStream.read(bytes);
//創建回應報文的回應頭
HttpHeaders httpHeaders = new HttpHeaders();
//設定瀏覽器處理該回應的方式,attachment是附件形式,后面跟客戶端下載該檔案的名字
//如果客戶端下載檔案會出現名字亂碼,則應轉換一下編碼
//httpHeaders.add("Content-Disposition", "attachment;filename="+ URLEncoder.encode(fileName,"utf-8"));
httpHeaders.add("Content-Disposition", "attachment;filename="+ fileName);
//application/octet-stream: 二進制流資料(最常見的檔案下載)
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//設定回應狀態
HttpStatus httpStatus = HttpStatus.OK;
return new ResponseEntity<byte[]>(bytes,httpHeaders,httpStatus);
}
12、注解配置SpringMVC
完全使用配置類和注解代替web工程的組態檔web.xml和SpringMVC、Spirng的xml組態檔的功能,
在Servlet3.0環境中,容器會在類路徑中查找實作javax.servlet.ServletContainerInitializer介面的類,如果找到的話就用它來配置Servlet容器,
Spring提供了這個介面的實作,名為SpringServletContainerInitializer,這個類反過來又會查找實作WebApplicationInitializer的類并將配置的任務交給它們來完成,Spring3.2引入了一個便利的WebApplicationInitializer基礎實作,名為AbstractAnnotationConfigDispatcherServletInitializer,當我們的類擴展了AbstractAnnotationConfigDispatcherServletInitializer并將其部署到Servlet3.0容器的時候,容器會自動發現它,并用它來配置Servlet背景關系,

撰寫WebConfig
- WebConfig.class用來代替web工程組態檔web.xml,
- 繼承了AbstractAnnotationConfigDispatcherServletInitializer.
package com.zh.config;
import com.zh.filter.MyFilter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置類
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 指定SpringMVC的配置類
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
/**
* 指定DispatcherServlet的映射規則,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
*這里配置的過濾器是和DispatcherServlet一樣,
* 也就是所有經過DispatcherServlet的都會經過這些過濾器
* 如果需要增加自己定義的過濾器,則可以使用注解@WebFilter
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("utf-8");
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
MyFilter myFilter = new MyFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter,myFilter};
}
}
以上配置過濾器的過濾路徑是和DispatcherServlet的映射規則一樣,如果還需要添加自己的過濾去,則需要注解配置路徑
注:@WebFilter 注解是 servlet3.0 提供的注解,另外
關于匯入serv依賴的時候出現的問題
servlet.jar 是servlet 3.0 版本之前的地址
javax.servlet-api.jar 是servlet 3.0 版本之后的地址
此配置類對應了web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<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>
<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>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置了一個servlet(dispatcherServlet)和兩個過濾器characterEncodingFilter,HiddenHttpMethodFilter
撰寫SpringMVCConfig
需要配置如下內容
包掃描
注解驅動
靜態資源處理
視圖決議器
攔截器
例外處理器
上傳檔案決議器
訊息轉換器
view-controller
package com.zh.config;
import com.zh.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
@Configuration
//包掃描
@ComponentScan("com.zh")
//開啟注解驅動
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
//靜態資源處理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//視圖決議器
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();
resourceViewResolver.setPrefix("/WEB-INF/jsp/");
resourceViewResolver.setSuffix(".jsp");
return resourceViewResolver;
}
//配置攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
//配置例外映射
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//設定例外映射
exceptionResolver.setExceptionMappings(prop);
//設定共享例外資訊的鍵
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
//檔案上傳決議器,需要其他依賴commons-fileupload
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
//配置訊息轉換器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
//設定應用型別和字符編碼
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(new MediaType(MediaType.APPLICATION_JSON,Charset.forName("utf-8")));
mediaTypeList.add(new MediaType(MediaType.TEXT_HTML,Charset.forName("utf-8")));
//設定次轉換器支持的型別
stringHttpMessageConverter.setSupportedMediaTypes(mediaTypeList);
converters.add(stringHttpMessageConverter);
}
//相當于<mvc:view-controller path="/" view-name="index"/>
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
}
13、SpringMVC執行流程
1、SpringMVC常用組件
- DispatcherServlet:前端控制器,不需要工程師開發,由框架提供
作用:統一處理請求和回應,整個流程控制的中心,由它呼叫其它組件處理用戶的請求
- HandlerMapping:處理器映射器,不需要工程師開發,由框架提供
作用:根據請求的url、method等資訊查找Handler,即控制器方法
- Handler:處理器,需要工程師開發
作用:在DispatcherServlet的控制下Handler對具體的用戶請求進行處理
- HandlerAdapter:處理器配接器,不需要工程師開發,由框架提供
作用:通過HandlerAdapter對處理器(控制器方法)進行執行
- ViewResolver:視圖決議器,不需要工程師開發,由框架提供
作用:進行視圖決議,得到相應的視圖,例如:ThymeleafView、InternalResourceView、RedirectView
- View:視圖
作用:將模型資料通過頁面展示給用戶
2、DispatcherServlet呼叫組件處理請求
a>processRequest()
FrameworkServlet重寫HttpServlet中的service()和doXxx(),這些方法中呼叫了processRequest(request, response)
所在類:org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 執行服務,doService()是一個抽象方法,在DispatcherServlet中進行了重寫
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
b>doService()
所在類:org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 處理請求和回應
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
c>doDispatch()
所在類:org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
mappedHandler:呼叫鏈
包含handler、interceptorList、interceptorIndex
handler:瀏覽器發送的請求所匹配的控制器方法
interceptorList:處理控制器方法的所有攔截器集合
interceptorIndex:攔截器索引,控制攔截器afterCompletion()的執行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通過控制器方法創建相應的處理器配接器,呼叫所對應的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 呼叫攔截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由處理器配接器呼叫具體的控制器方法,最侄訓得ModelAndView物件
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 呼叫攔截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 后續處理:處理模型資料和渲染視圖
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
d>processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 處理模型資料和渲染視圖
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 呼叫攔截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
3、SpringMVC的執行流程
此圖自己總結的:

-
用戶向服務器發送請求,請求被SpringMVC 前端控制器 DispatcherServlet捕獲,
-
DispatcherServlet對請求URL進行決議,得到請求資源識別符號(URI),判斷請求URI對應的映射:
-
不存在
i. 再判斷是否配置了mvc:default-servlet-handler
ii. 如果沒配置,則控制臺報映射查找不到,客戶端展示404錯誤
iii. 如果有配置,則訪問目標資源(一般為靜態資源,如:JS,CSS,HTML),找不到客戶端也會展示404錯誤
-
存在則執行下面的流程
-
根據該URI,呼叫HandlerMapping獲得該Handler配置的所有相關的物件(包括Handler物件以及Handler物件對應的攔截器),最后以HandlerExecutionChain執行鏈物件的形式回傳,
-
DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter,
-
如果成功獲得HandlerAdapter,此時將開始執行攔截器的preHandler(…)方法【正向】
-
提取Request中的模型資料,填充Handler入參,開始執行Handler(Controller)方法,處理請求,在填充Handler的入參程序中,根據你的配置,Spring將幫你做一些額外的作業:
-
HttpMessageConveter: 將請求訊息(如Json、xml等資料)轉換成一個物件,將物件轉換為指定的回應資訊
-
資料轉換:對請求訊息進行資料轉換,如String轉換成Integer、Double等
-
資料格式化:對請求訊息進行資料格式化, 如將字串轉換成格式化數字或格式化日期等
-
資料驗證: 驗證資料的有效性(長度、格式等),驗證結果存盤到BindingResult或Error中
-
-
Handler執行完成后,向DispatcherServlet 回傳一個ModelAndView物件,
-
此時將開始執行攔截器的postHandle(...)方法【逆向】,
-
根據回傳的ModelAndView(此時會判斷是否存在例外:如果存在例外,則執行HandlerExceptionResolver進行例外處理)選擇一個適合的ViewResolver進行視圖決議,根據Model和View,來渲染視圖,
-
渲染視圖完畢執行攔截器的afterCompletion(…)方法【逆向】,
-
將渲染結果回傳給客戶端,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540599.html
標籤:其他
上一篇:Python從入門到精通(第2版)——pyuic5: error: no such option: -m的問題解決
下一篇:面向物件與面向程序
