SpringMVC
一、SpringMVC概述
- Spring為展現層面提供基於 MVC 設計理念的 Web 框架
- SpringMVC 通過一套 MVC 注解,讓 POJO 成為處理請求的控制器,而無需實現任何接口。
注:POJO( Plain Old Java Object : 普通的Java對象 ) - 支持 REST(restful) 風格的 URL 請求。
- 采用松散耦合可插拔的組件結構,比其他 MVC 框架更具擴展性和靈活性。
注:MVC,M是模型層,用於裝載傳輸數據,V 是視圖層,用於展示數據,C是控制層,是指控制器接受用戶的請求並調用模型和視圖去完成用戶的需求,控制器本身不輸出任何東西和做任何處理。它只是接收請求並決定調用哪個模型構件去處理請求,然后再確定用哪個視圖來顯示返回的數據。
二、SpringMVC 是什么
- 一種輕量級的,基於 MVC 的 Web 層應用框架,偏前端,而不是基於業務邏輯層,Spring 框架的一個后續產品。
三、SpringMVC 能干什么
- 天生與Spring框架集成,如(IOC,AOP)
- 支持Restful風格
- 使得 Web 開發更簡潔
- 支持靈活的 URL 到頁面控制器的映射
- 容易與其他視圖技術集成,如(Velocity,FreeMarker 等)
- 因模型數據不存放在特定的 API 而是放在一個 Module 里面( Map數據結構實現 )因此容易被其他框架使用
- 非常靈活的數據驗證,格式化和數據綁定機制,能使用任何對象進行數據綁定,不必實現特定框架的 API
- 強大簡單的異常處理
- 對靜態資源的支持
- 支持靈活的本地化,主題等解析
四、SpringMVC怎么用
SpringMVC 的搭建過程
1.創建 web 項目,導入 jar 包
2.配置 web.xml
在 web.xml 中來配置 SpringMVC 的核心控制器( 前端控制器:DispatcherServlet )
配置如下:
<?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>spring_mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring_mvc</servlet-name>
<!--若是只有 / ,那么只有訪問為一個請求時,才會被該 Servlet 程序處理-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
作用:加載 SpringMVC 的配置文件。在上面的配置方式下,核心控制器會自動的加載配置文件(配置文件需要手動創建,Spring只負責自動加載),此時的配置文件有默認的位置和名稱,默認位置為 WEB-INF下,默認名稱為就為上面配置文件中 servlet-name 的值 - servlet.xml,如 spring_mvc-servlet.xml。
如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--掃描組件,將加上 @Controller 注解的類作為 SpringMVC 的控制層-->
<context:component-scan base-package=""></context:component-scan>
<!--配置視圖解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前綴-->
<property name="prefix" value="/WEB_INF/view/"></property>
<!--配置后綴-->
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
當加載完配置文件,spring_mvc就會根據掃描組件,找到控制層。
創建一個POJO( JavaBean ),在此類上加上 @Controller 注解,SpringMVC就會將此類作為控制層,處理請求和響應。
在控制層中,需要在對應的方法上設置注解 @RequestMapping ( value = "xxx" ),SpringMVC就是通過此注解將請求路徑與控制層中的對應方法進行匹配,此時請求路徑為 :localhost:8080/項目名稱/xxx 。
處理請求的方法會返回一個字符串(這是其中一種方式) 即視圖名稱,最后會通過配置文件中配置的視圖解析器;來實現視圖跳轉。例如:
<!--配置視圖解析器
作用:將 prefix(/WEB-INF/view/) + 視圖名稱(success) + suffix(.jsp) 來確定最終要跳轉的頁面,如下
/WEB-INF/view/success.jsp
-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前綴-->
<property name="prefix" value="/WEB-INF/view/"></property>
<!--配置后綴-->
<property name="suffix" value=".jsp"></property>
</bean>
若請求地址值中附帶有參數時,例如:hello?username=lisi&password=1222 ,那么在對應的控制器方法中,加上與請求地址的參數名稱相同的方法參數即可,例如:public String HelloWorld(String username, String password) { } ,若對應的屬性在一個實體 Bean 中存在,則可以直接在方法參數中添加對應的 Bean 對象參數,Spring 能夠自動的獲取請求的參數並賦值到該對象中。
常見注解以及使用過程(1)
-
@RequestMapping 注解
該注解除了修飾方法還能夠修飾類,同時修飾類時和方法時,方法中的@RequestMapping訪問路徑其實是相對於類上面的@RequestMapping中的路徑的,而類上@RequestMapping中的路徑則相對於Web項目的根路徑 /。例如:
@Controller //控制器 @RequestMapping("/SpringMVC") //類定義處 public class SpringMVCTest2 { private static final String successs = "success"; @RequestMapping("/tesrRequestMapping")//方法定義處 public String tesrRequestMapping() { System.out.println("tesrRequestMapping"); return successs; } }若類定義處沒有定義 @RequestMapping ,則方法定義處中的路徑就直接相對於 Web項目的 根目錄 / 。
使用 method 屬性指定請求方式
//使用 method 屬性來指定請求方式 @RequestMapping(value = "/testMethod" ,method = RequestMethod.POST) public String testMethod(){ System.out.println("testMethod"); return successs; } -
使用@RequestMapping 來映射請求( params 和 handers 映射)

代碼如下:
//可以使用 params 和 handers 來更加精確的映射請求 , params 和 handers 支持簡單的表達式。 @RequestMapping(value = "/testParamsAndHeaners", params = {"username", "age!=10"} ,headers = {"Accept-Language: eh-US,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"}) public String testParamsAndHeaners() { System.out.println("testParamsAndHeaners"); return successs; } -
使用@RequestMappping映射請求2(Ant 路徑)

-
@PathVariable 映射 URL 綁定的占位符

代碼實例:
/* @PathVariable 該注解可以將請求路徑中的指定部分,通過占位符${xxx}映射成目標方法中的參數, 請求地址為:SpringMVC/testPathVariable/1 */ @RequestMapping(value = "/testPathVariable/{id}") public String testPathVariable(@PathVariable("id") Integer id) { System.out.println("testPathVariable "+id); return successs; }REST
REST簡介

示例:

REST風格請求代碼如下:<%--表現層狀態轉化 單純的 http 協議是一個無狀態協議,資源在服務器上沒有變化,而當加上不同類型的請求時 例如 get 請求,就代表獲取服務器上的數據,這時,服務器上的數據收到影響,而表現層代表 把資源呈現出來的形式,如文本形式,圖片形式,HTML形式,通過 get 請求獲取數據,在表現層上進行呈現 使得表現層的狀態發生變化,就叫表現層狀態轉化,這種轉發,在SpringMVC中可以通過 HiddenHttpMethodFilter 來實現將post請求轉化為不同的請求,影響表現層,實現表現層狀態轉化。 --%> <%--get請求--%> <a href="SpringMVC/testRestGet/1">RestGet</a> <br> <br> <%--post請求--%> <form action="SpringMVC/testRestPost" method="post"> <input type="submit" value="testPost"> </form> <br> <%--put請求--%> <form action="SpringMVC/testRestPut/2" method="post"> <%--通過name屬性標識為一個請求,通過 value 屬性值指定請求的類型--%> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="testPUT"> </form> <%--delete請求--%> <form action="SpringMVC/testRestDelete/3" method="post"> <%--通過name屬性標識為一個請求,通過 value 屬性值指定請求的類型--%> <input type="hidden" name="_method" value="DELETE"> <input type="submit" value="testDelete"> </form>接收的代碼
/* 表現層狀態轉化 HiddenHttpMethodFilter */ //指定請求方式為 get @RequestMapping(value = "/testRestGet/{id}", method = RequestMethod.GET) public String testRestGet(@PathVariable("id") Integer id) { System.out.println("testRestGet:" + id); return successs; } //指定請求方式為 post @RequestMapping(value = "/testRestPost", method = RequestMethod.POST) public String testRestPost() { System.out.println("testRestPost"); return successs; } //指定請求方式為 put @ResponseBody //對於Tomcat8版本,並不支持 PUT,DELETE 請求,需要在控制器方法上加上該注解 @RequestMapping(value = "/testRestPut/{id}", method = RequestMethod.PUT) public String testRestPut(@PathVariable("id") Integer id) { System.out.println("testRestPut" + id); return successs; } //指定請求方式為 delete @ResponseBody @RequestMapping(value = "/testRestDelete/{id}", method = RequestMethod.DELETE) public String testRestDelete(@PathVariable("id") Integer id) { System.out.println("testRestDelete:" + id); return successs; }常見注解及使用過程(2)
-
@RequestParam注解


代碼演示:
/*
通過 @RequestParam 獲取請求路徑中的指定名稱的參數
其中 value 屬性代表獲取的參數名
required 代表參數是否必需的,默認為 true ,當值為 true 時,若請求中沒有該參水,則會產生異常
defaultValue 代表請求中沒有參數時的默認值
*/
@RequestMapping(value = "/testRequestParam")
public String testRequestParam(@RequestParam(value = "username", required = false, defaultValue = "0")
String username, @RequestParam("age") Integer age) {
System.out.println("user:" + username + "\n" + "age:" + age);
return successs;
}
-
@RequestHeader 注解

-
@CookieValue 注解

使用POJO對象綁定請求參數

代碼演示:
/*
使用POJO作為參數
直接設置參數為實體對象即可,Spring會自動進行屬性注入和級聯屬性注入
實際上又是通過空參構造器創建對象,調用對象中的 set 方法注入屬性。
*/
@RequestMapping(value = "/testPOJO", method = RequestMethod.POST)
public String testPOJO(User user) {
System.out.println("testPOJO:\n" + "username:" + user.getUsername() + "\n" + "password:" + user.getPassword()
+ "\nemail:" + user.getEmail() + "\nage:" + user.getAge() + "\nprovince:" + user.getAddress().getProvince() + "\n"
+ "\ncity:" + user.getAddress().getCity() + "\n");
return SUCCESS;
}
請求參數:
<%--使用POJO對象作為參數
Spring自動根據參數名 name 的值,匹配實體對象中的屬性名,進行賦值和級聯賦值
因此參數名 name 的值必須和 POJO 中對應的屬性名一致,否則無法注入。
--%>
<h3>使用POJO對象作為參數的測試</h3>
<form action="SpringMVC/testPOJO" method="post">
username: <input type="text" name="username">
<br>
password: <input type="password" name="password">
<br>
email: <input type="text" name="email">
<br>
age: <input type="text" name="age">
<br>
<%--級聯屬性,通過 屬性對象.屬性值 來設置 --%>
province: <input type="text" name="address.province">
<br>
city: <input type="text" name="address.city">
<input type="submit" value="提交">
</form>
使用 Servlet 原生 API 作為參數

代碼如下:
/*
使用原生 Servlet 作為參數
*/
@RequestMapping(value = "/testServletAPI")
public String testServletAPI(HttpServletRequest request, HttpServletResponse response) {
System.out.println(request + "\n" + response);
return SUCCESS;
}
ModelAndView處理模型數據

代碼演示:
/*
使用ModelAndView處理模型數據
目標方法的返回值可以是模型數據和和視圖信息
*/
@RequestMapping(value = "/testModuelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型數據到ModuelAndView中,實際上是將數據模型 module 放入 request 域中,通過鍵值調用
modelAndView.addObject("time",new Date());
return modelAndView;
}
請求成功后,通過鍵在 request 調用:
${ requestScope.time }
Map處理模型數據

代碼演示
/*
Map處理數據模型
在目標方法中添加Map類型的參數,實際上也可以是 Moduel 或 ModuelMap 類型
實質是將數據放入ModuelAndView中的ModuelMap中
獲取數據時,也是可以通過 request 域對象獲取
*/
@RequestMapping(value = "/testMap")
public String testMap(Map<String, Object> map) {
map.put("names", Arrays.asList("tom", "jery", "jack", "Mike"));
return SUCCESS;
}
處理模型數據之SessionAttributes

/*
@SessionAttributes 該注解可以通過 value 和 types 指定某種類型的模型數據放入Session域中,
同時也放入Request域中,value 和 types 的參數可以有多個
注:該注解只能使用在類上,不能使用於方法上
*/
@SessionAttributes(value = {"user", "user1"}, types = {User.class, Address.class})
代碼演示
/*
處理數據模型之 @SessionAttributes 注解
*/
@RequestMapping(value = "testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
User user = new User("Mike", "111111", "Mike@163.com", 16);
map.put("user", user);
return SUCCESS;
}
實際使用時,可以同時在 request 和 session 域中獲取到
req:${requestScope.user} <br>
Session:${sessionScope.user} <br>
@ModelAttribute 注解
被該注解標記的方法會在每個目標方法執行前被 SpringMVC 調用
代碼演示:
/*
有 @ModelAttribute 注解標記的方法,會在每個目標方法執行前被 SpringMVC 調用
*/
@ModelAttribute
public void getUser(@RequestParam(value = "id", required = false) String id,
Map<String, Object> map) {
//模擬從數據庫獲取數據
User user = new User("1", "Tom", "12121212", "192812@13.com", 10, new Address("dwdw", "dwdwd"));
System.out.println("模擬數據庫獲取user:" + user);
map.put("user", user);
}
/*
@ModuelAttribute應用場景
*/
@RequestMapping(value = "/testModuelAttribute", method = RequestMethod.POST)
public String testModuelAttribute(User user) {
System.out.println("修改user:" + user);
return SUCCESS;
}
頁面代碼:
<h3>使用@ModuelAttribute注解</h3>
<%--
1.要求密碼不能被修改
2.直接在表單回顯修改后的數據
--%>
<form action="SpringMVC/testModuelAttribute" method="post">
<input type="hidden" name="id" value="1">
username: <input type="text" name="username" value="Tom">
<br>
email: <input type="text" name="email" value="11223@1212.com">
<br>
age: <input type="text" name="age" value="12">
<br>
province: <input type="text" name="address.province" value="sichuan">
<br>
city: <input type="text" name="address.city" value="deyang">
<input type="submit" value="提交">
</form>
@ModelAttribute運行原理
1.運行流程
(1) 執行 @ModeAttribute 修飾的方法,獲取模型數據,把模型數據放入 Map 中,鍵為 :user
(2) SpringMVC從 Map 中取出 user 對象,並把表單的請求參數賦給該 user 對象的對應屬性
(3) Spring把對象傳入目標方法的參數 user 中
注:在 @MudelAttribute 修飾的方法中,放入到 Map 中的數據的鍵,需要和目標方法入參的類型首字母小寫的字符串相同。
2.@ModelAttribute源碼分析
(1) 調用 @ModelAttribute 注解修飾的方法,實際上把 @ModelAttribute 方法中的 Map 數據放在了 implicitModel 中
(2) 解析請求處理器的目標參數,實際上該目標參數來自於 WebDataBinder 對象的 target 屬性
創建 WebDataBinder 對象
① 確定 objectName屬性:
若傳入的 attrName 屬性值為空,則 objectName 為類名第一個字母小寫
注:若目標方法的 Bean 屬性值使用了 @ModelAttribute 來修飾,則 attrName 值即為 @ModelAttribute 的 value 值。
② 確定 terget 屬性:
在 impliciiModel 中查找 attrName 對應的屬性值,若不存在,則驗證當前 handler 是否使用了 @SessionAttributes 進行 修飾,若使用了,則嘗試從 Session 中獲取 attrName 對應的屬性值,若 Session 沒有對應的屬性,則拋出異常。
若 handler 沒有使用 @SessionAttributes 進行修飾,或 @SesssionAttribuutes 中沒有使用 value 值指定的鍵和attrName 相互匹配,則通過反射創建 Bean 對象。
(3) SpringMVC把請求表單的請求參數賦給了 WeDataBinder 的 target 對應的屬性
(4) SpringMVC 把 WebDataBinder 的 attrName 和 target 給到 implicitModel,進而傳導 Request 域對象中
(5) 把 WebDataBinder 的 target 作為參數傳遞給目標方法的對應入參
3.SpringMVC 確實目標方法 Bean 類型入參的過程
(1) 確定一個 key:若目標方法的Bean類型的參數沒有使用 @ModelAttribute 進行修飾,則使用 Bean 名稱的第一個字母小寫來作為 key,若使用了 @ModelAttribute 來修飾,則使用 @ModelAttribute 注解的 value 屬性值來作為 key
(2) 在 implicitModel 中查找 key 對應的對象,若存在,則作為入參傳入,(若在 @ModelAttribute 標記方法中的Map中保存過 key 且和 之前確定的 key 一致,則會獲取到這個對象), 若不存在 key 對應的對象,則檢查當前的 handler 是否使用 @SessionAttibute 注解修飾,若使用了該注解,且這個注解的 value 屬性值中,包含了 key ,則會從 HttpSession 中來獲取 key 所對應的 value 值,若存在,則直接傳入到目標方法的入參中,若不存在,則拋出異常。
若handler 沒有標識 @SsessionAttribute ,或 @SessionAttribute的value值中不包含 key,則通過反射來創建 Bean 類型的參數,傳入為目標方法的入參
(3) SpringMVC會把 key 和對應的 value(Bean類型的對象) 保存到 implicitModel中,進而會保存到 request 中。
注: handler 是對 Controller 的 Bean 本身和請求 Method( 目標方法 ) 的包裝,也是一個類,有實例化的對象。
@ModelAttribute 修飾 Bean 類型目標方法的入參
1.被 @MudelAttribute 注解修飾的方法,會在每個目標方法執行之前被 SpringMVC 調用
2.@ModelAttribute 注解也可以用來修飾目標方法 Bean 類型的入參,其 value 屬性值有如下作用
> SpringMVC會利用 value 屬性值在 implicitModel 中查找對應的鍵值,若存在,會傳入到目標方法的入參中
> SpringMVC會以 @ModelAttribute 的 value 為 key ,Bean 類型的對象為 value 存入 request 中
@SessionAttributes注解引發的異常
1.在沒有 @ModelAttribute 修飾的方法時,目標方法使用 Bean 類型作為入參,SpringMVC會在 implicitModel 中查找對應的鍵值為該目標方法的入參賦值,若沒有找到,又會去檢查 handler 是否使用了 @SeeionAttributes 注解修飾,若使用了且 value 屬性的值中包含 對應的 key ,那么就會從 HttpSessoin 中根據 key 獲取對應的值,若值不存在或為空,就會拋出由 @SessionAttribute 注解引發的異常,意為 Session沒有對應的屬性值,此時只要為目標方法的對應入參加上 @ModelAttribute 注解,通過 value 指定 key 的值與 @SessionAttribute 注解中的 value 值不一樣即可。
2.在有 @ModelAttribute 注解修飾的方法的情況下,此異常不會出現。
注:實際上,直接使用 POJO 作為入參時,使用的也是如上的流程來為 Bean 類型的入參賦值,當@SessionAttributes 和 @ModelAttribute 注解存在的前提下,首先會根據這兩個注解先使用如上流程后,再結合請求地址中的參數來為目標方法的入參賦值,否則若沒有這兩個注解,則直接通過反射與結合請求地址中的參數來為目標方法的入參賦值。
視圖解析器流程分析




SpringMVC_JstlView

mvc:view-controller 標簽
<!--配置直接轉發的頁面
可以直接響應轉發的頁面,而無需經過任何控制器和控制器中的目標方法
path:訪問的路徑(相當於RequestMapping(“/hello”))
view-name:是你所要的視圖(如hello.jsp,相當於return “hello”) 配置了這個后對於/hello請求,
就會直接交給dispatcherServlet,然后使用ViewResolver進行解析。
注意:
使用了這個標簽后必須配置 <mvc:annotation-driven />
否則會造成所有的@Controller注解無法解析,導致404錯誤。
如果請求存在處理器,則這個標簽對應的請求處理將不起作用。因為請求是先去找處理器處理,如果找不到才會去找這個標簽配置。
-->
<mvc:view-controller path="/success" view-name="success"></mvc:view-controller>
<mvc:annotation-driven></mvc:annotation-driven>
自定義視圖
1.自定義一個視圖類,實現 View 接口,實現其中方法,代碼如下:
import org.springframework.web.servlet.View;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/*
自定義視圖類
注:視圖類以及視圖類所屬的包,必須和控制器以及所屬的目標方法在同一個包下,否則無法訪問到該視圖
*/
public class HelloView implements View {
//返回視圖的類型
@Override
public String getContentType() {
return "text/html";
}
//渲染視圖
@Override
public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.setContentType("text/html; charset=utf-8");
httpServletResponse.getWriter().write("<h4>自定義視圖</h4>");
}
}
2.配置視圖解析器
<!--配置自定義視圖的視圖解析器:BeanNameViewResolver
自動根據視圖類的的類名來解析視圖,觀察源碼發現,BeanNameViewResolver 通過
視圖類的實例來解析視圖,因此,需要對視圖類加上實例化的注解,如:@Component,
注:因為是通過視圖名來實例化視圖類,因此在沒有指定 @Component 注解的 value 值的前提下,
默認視圖名為視圖類名首字母小寫
-->
<bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" ></bean>
3.為上面的視圖解析器設置優先級
<!--設置視圖解析器的優先級,值越小,優先級越高
沒有固定的值,根據實際情況來,小於InternalResourceViewResolver的優先級即可
而對於InternalResourceViewResolver的優先級,因為該解析器筆記常用,因此該解析器的優先級為 Integer 的最大值,排在最后
否則就會阻礙其他的視圖解析器
-->
<property name="order" value="100"></property>
對應優先級的問題,解析如下:
重定向

代碼如下:
/*
重定向操作
*/
@RequestMapping("/testRedirect")
public String testRedirect(){
System.out.println("testRedirect");
return "redirect:/index.jsp";//redirect:重定向 forword:轉發
}
基於 SpringMVC 快速搭建 RESTFUL風格的 CRUD
CRUD 需求一
CRUD 需求二
CRUD 需求三
CRUD 需求四
相關的類:
注:emptloyee:員工 ,department:部門
由於一些問題,該 CRUD 只觀看教程,不做演示
靜態資源的處理
<!--配置TomCat服務器中默認的 Servlet:DefaultServlet
注:當DefaultServlet所設置的<url-pattern>和開發人員所配置的 DispatecherServlet 的 <url-pattern>相同時,
以開發人員的Servlet優先
作用:當客戶端發送請求,由於DefaultServlet 的 <url-pattern> 和開發人員設置的<url-pattern> 都為 /
會先通過 DispatcherServlet 來處理請求,來找該請求是否有相對應的處理器,有則處理,無則調用 DefaultServlet來處理
-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!--MVC驅動,其中一個功能就是和 DefaultServlet 搭配使用來處理靜態資源的釋放-->
<mvc:annotation-driven></mvc:annotation-driven>
數據綁定的流程
參考如下連接:https://www.cnblogs.com/softidea/p/10079869.html
自定義類型轉換器
SpringMVC上下文自帶的類型轉換
自定義的類型轉換

1.創建一個自定義的轉換器類,實現轉換器接口:Converter<S,T> ,使用注解進行實例化並實現數據轉換的具體方法
import Bean.Address;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class AddressConverter implements Converter<String, Address> {
@Override
public Address convert(String s) {
if (s != null) {
//按照指定分隔符分割為數組
String[] vals = s.split("-");
if (vals.length == 2) {
return new Address(vals[1], vals[0]);
} else {
return new Address("默認", "默認");
}
}
return null;
}
}
2.將自定義數據轉換類添加到配置文件:
<!--配置 Converter 數據類型轉換器
1.配置 ConversionServiceFactoryBean:轉換服務工廠
2.將自定義的數據類型轉換注入到 ConversionServiceFactoryBean 的 converters 屬性中,該屬性值為 set
可以在 property 中使用 set 標簽注入
-->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="addressConverter"></ref>
</set>
</property>
</bean>
3.將 ConversionServiceFactoryBean 添加到 MVC 驅動中
<!--MVC驅動,其中一個功能就是和 DefaultServlet 搭配使用來處理靜態資源的釋放-->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean" ></mvc:annotation-driven>
此時,一旦表單提交,便可以通過自定義的轉換器實現將指定的數據轉化成指定的類型的功能。
mvc:annotation-driven 配置
@InitBinder 注解
簡介
參考筆記(相對路徑地址): typora-user-images/從原理層面掌握@InitBinder的使用【享學Spring MVC】 - 雲+社區 - 騰訊雲.html
數據類型轉換
簡介
日期類型:
1. 配置 MVC 驅動 mvc:annotation-driven
2. 在對應的 bean 屬性上加上 DataTimeFormat 注解,注解值為日期的格式,例如 :yyy-MM-dd
數值類型:
1. 配置MVC驅動 mvc:annotation-driven
2. 在對應的屬性上加上 NumberFormat 注解 ,注解值為數值的格式,例如:#,###,###.# (使用 # 代替數值)
JSR303數據校驗
簡介

1.如何校驗

① 使用 JSR303 驗證標准
② 加入 hibernate validator 驗證框架
③ 在 SprinvMVC 配置文件中,加上 mvc:annotation-driven 驅動配置
④ 在對應 bean 屬性上添加注解
⑤ 在目標方法 bean 類型的參數前面加上 @Valid 注解
2.驗證出錯跳轉的頁面

例如:
/*
數據校驗測試
*/
@RequestMapping("/testDatavalidation")
//被 @Valid 注解修飾的校驗 bean ,必須和綁定結果 BindResult 或 Errors 成對出現,二者之間不允許什么其他入參
public String Datavalidation(@Valid Book book, Errors result) {
System.out.println("Book:" + book);
if (result.getErrorCount() > 0) {
//遍歷顯示錯誤信息
for (FieldError fieldError : result.getFieldErrors()) {
System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage());
}
//如果驗證出錯,則轉向定制頁面,重定向和轉化都可以
return "redirect:/index.jsp";
}
return SUCCESS;
}
SpringMVC返回JSON
1.前端 jsp 頁面中,使用 JQuery 發起請求,請求的數據為 JSON 格式,代碼如下
<h3>SpringMVC處理JSON</h3>
<script>
window.onload = function () {
$(function () {
$('#testJSON').click(function () {
var url = this.href;//請求的地址
var ages = {};//請求的參數
$.post(url, ages, function (data) { //發起 POSt 請求,獲取 JSON 格式的數據:data
for (let i = 0; i < data.length; i++) { //data 為數組格式
var bookName = data[i].bookName;
var bookPrice = data[i].bookPrice;
alert("book" + i + ":" + bookName + "-" + bookPrice);
}
});
})
return false;
})
}
</script>
<a href="SpringMVC/testJson" id="testJson">testJSON</a>
2.導入相關的 jar 資源包:
3.編寫目標方法:
/*
SpringMVC處理JSON,
*/
@RequestMapping("/testJson")
@ResponseBody //加上該注解,@ResponseBody的作用其實是將java對象轉為json格式的數據。
public List<Book> testJson() { //直接返回需要的數據即可,不一定非是集合
List<Book> list = new ArrayList<>();
list.add(new Book("水滸傳", new BigDecimal("562.33")));
list.add(new Book("紅樓夢", new BigDecimal("134.33")));
list.add(new Book("西游記", new BigDecimal("155.33")));
list.add(new Book("三國演義", new BigDecimal("222.33")));
return list;
}
使用HttpMessageConverter(Http消息轉換器,負責轉換請求和響應的消息)
簡介
使用示例:
解析:當使用 @RequestBody 修飾入參時,就根據入參的類型選擇 HttpMessageConverter 的實現類,當使用 @ResponseBody 時,則根據目標方法的返回值類型來選擇 HttpMessageConverter 實現類。
代碼測試:
/*
HttpMessageConverter使用測試
*/
@RequestMapping("/testHttpMessageConverter")
@ResponseBody //將當前目標方法 return 的數據通過 HttpOutputMessage 輸出流響應到客戶端
//@RequestBody 將請求的參數轉化為其修飾的入參類型相同的數據類型,賦值給對應的入參
public String testHttpMessageConverter(@RequestBody String body) {
System.out.println(body);
return "current:" + new Date();
}
@ResponseEntity測試(文件下載)
簡介
/*
ResponseEntity使用測試,可由於處理目標方法入參和返回值
*/
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws Exception {
byte[] bytes;
ServletContext servletContext = session.getServletContext();
InputStream ins = servletContext.getResourceAsStream("/resource/jquery-3.5.1.js");
/*
要一次讀取多個字節時,經常用到InputStream.available()方法,這個方法可以在讀寫操作前先得知數據流里有多少個字節可以讀取。
需要注意的是,如果這個方法用在從本地文件讀取數據時,一般不會遇到問題,但如果是用於網絡操作,就經常會遇到一些麻煩。比如,Socket通訊時,
對方明明發來了1000個字節,但是自己的程序調用available()方法卻只得到900,或者100,甚至是0,感覺有點莫名其妙,怎么也找不到原因。
其實,這是因為網絡通訊往往是間斷性的,一串字節往往分幾批進行發送。本地程序調用available()方法有時得到0,這可能是對方還沒有響應,也可能是對方已經響應了,
但是數據還沒有送達本地。對方發送了1000個字節給你,也許分成3批到達,這你就要調用3次available()方法才能將數據總數全部得到。
*/
bytes = new byte[ins.available()];
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=jquery-3.5.1.js");
HttpStatus status = HttpStatus.OK;
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, status);
return responseEntity;
}
國際化
關於國際化:
1.在頁面上能夠根據瀏覽器的語言設置情況對文本(不是內容),時間,數值,進行本地化處理
2.可以在 Bean 中獲取國際化資源文件 Local 對應的消息
3.可以通過超鏈接切換 Local ,而不再依賴瀏覽器的語言設置情況
解決:
1.使用 JSTL 的 fmt 標簽
2.在 Bean 中注入 ResourceBundleMessageSource 的示例,使用其對應的 getMessage 方法即可
3.配置 LocalResolver 和 LocaleChangeInterceptor
文件上傳
簡介
1.導入相關 jar 包
2.配置上傳解析器
<!--配置文件上傳解析器(該解析器的 id 必須為:multipartResolver)-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--默認字符集-->
<property name="defaultEncoding" value="UTF-8"></property>
<!--最大上傳大小-->
<property name="maxUploadSize" value="102400000"></property>
</bean>
3.編寫目標方法
/*
文件上傳
使用 @RequestParam 注解獲取請求參數中的上傳文件數據
使用 MultipartFile 類型的入參來接收文件
*/
@RequestMapping("/testFileUplopa")
public String testFileUplopad(@RequestParam("file") MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
System.out.println(file.getInputStream());
InputStream inputStream = file.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("D:\\" + file.getOriginalFilename()));
//保存上傳文件到服務器
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
return SUCCESS;
}
注:
<!--在文件上傳的表單中,必須加上 enctype="multipart/form-data" 否則瀏覽器會報 500 的錯誤-->
<form action="SpringMVC/testFileUplopa" method="post" enctype="multipart/form-data">
UpLoad: <input type="file" name="file"> <br>
<input type="submit" value="上傳">
</form>
報錯如下:
攔截器
自定義攔截器
1.自定義一個攔截器類,實現 Interceptor 接口並重寫其中的方法
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
自定義第一個攔截器
*/
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("preHandle:預先處理");
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle:后期處理");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("afterCompletion:完成后");
}
}
2.在配置文件中進行配置
<!--
配置自定義的攔截器
在該標簽中添加自定義攔截器的全類名路徑即可
-->
<mvc:interceptors>
<bean id="firstInterceptor" class="SpringMVCTest.MyInterceptor.FirstInterceptor"></bean>
</mvc:interceptors>
攔截器的配置
1.通過攔截映射配置攔截指定請求路徑和排除指定請求路徑的攔截器
<!--配置一個攔截映射,攔截(或不攔截)指定路徑-->
<mvc:interceptor>
<!--
<mvc:exclude-mapping path=""/>
不攔截(排除)該 path 值的請求路徑
-->
<!--攔截該 path 值的請求路徑-->
<mvc:mapping path="/success"/>
<!--指定一個攔截器對象-->
<bean id="secondInterceptor" class="SpringMVCTest.MyInterceptor.SecondInterceptor"></bean>
</mvc:interceptor>
注:在配置 攔截映射 的情況下,當請求路徑為攔截器映射中的指定路徑時,由攔截器映射指定的攔截器對象便會執行,同時,其余的攔截器也會執行,當請求路徑不是攔截器映射中的指定路徑時,只執行其余攔截器,該 映射攔截器 不會執行,在配置 排除型攔截映射 的情況下,攔截和執行規則反之。
2.多個攔截方法的執行順序
執行順序圖示:
通過上圖以及源碼分析,對於多個攔截器,針對 preHandler() 方法,按照攔截器配置順序的順序執行,對於 postHandler()方法 按照攔截器配置順序的倒序執行,對於 afterCompletion()方法,按照攔截器配置順序的倒序執行。
跟詳細的執行過程,可參考:https://blog.csdn.net/shijiujiu33/article/details/88146502
異常處理
簡介

代碼演示
/*
處理異常
通過 @ExceptionHandler(異常處理程序) 注解來處理異常,該注解的 value 屬性中
通過一個異常類型的 class 數組來規定可以處理的異常類型
被 @ExceptionHandler 注解標注的方法體內可以執行輸出異常信息
跳轉異常頁面等操作。
注:
方法的入參為 Exception 類型時,SpringMVC會自動將 Exception 異常對象注入該入參
被 @ExceptionHandler 注解修飾的方法中,除了 Exception 不能傳入其他類型的入參
可以傳入多個 Exception 的入參,其被注入異常信息是相同的
若要向頁面傳遞異常信息,可以使用 ModelAndView 來實現
@ExceptionHandler 異常方法的優先級
當有多個異常處理的方法,且處理的異常類型具有包含關系時,會優先查找執行與異常接近的異常方法,
若沒有則向上查找異常方法
*/
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handlerArithmeticException(Exception ex) {
System.out.println("ArithmeticException-出現算術異常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex",ex);
return mv;
}
@ExceptionHandler(value = {RuntimeException.class})
public ModelAndView handlerArithmeticException2(Exception ex) {
System.out.println("RuntimeException-出現算術異常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex",ex);
return mv;
}
全局的異常處理
1.自定義一個異常處理類
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/*
ExceptionHandler測試(全局的異常處理)
*/
@ControllerAdvice
public class ExceptionHandlerDemo {
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView handlerArithmeticException(Exception ex) {
System.out.println("ArithmeticException-出現算術異常" + ex);
ModelAndView mv = new ModelAndView("errorView/error");
mv.addObject("ex", ex);
return mv;
}
}
注:關於 @ControllerAdvice 注解:https://www.cnblogs.com/lenve/p/10748453.html 使用該注解后,一些全局操作,例如全局異常處理,全局數據綁定等,可以在添加了該注解的類中進行執行。
對於全局異常操作,如果在當前 Handler 中找不到對應的 @ExceptionHandler 異常處理方法,則會去 @ContorllerAdvice 標記的類中去查找對應的異常處理方法。
異常處理:ResponseStatusExceptionResolver
簡介
帶有 @ResponseStatus 注解的異常類會被 ResponseStatusExceptionResolver 解析。可以實現自定義的一些異常,同時在頁面上進行顯示。具體的使用方法如下:
代碼演示:
/*
自定義異常類,圖書名稱為空
*/
/*
@ResponseStatus 注解
屬性
value:用於指定發生異常時,Http 的狀態碼
reson:表明發生異常的原因
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "圖書名為空!")
public class BookNameNullException extends RuntimeException {
//序列號
static final long serialVersionUID = 1L;
}
/*
ResponseStatusExceptionResolver(狀態響應異常解析器)測試
*/
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam(value = "bookName", required = false) String bookName) {
if (bookName == null) {
//拋出自定義的異常
throw new BookNameNullException();
}
System.out.println("圖書名稱合法,無異常!");
return SUCCESS;
}
響應結果:
拋出異常在沒有被 ExpetionHandlerResolver 解析的情況下,ResponseStatusExceptionResolver 會解析被 @ResponseStatus 標識的異常類,根據 @ResponseStatus 的中的 響應狀態碼 value 和異常原因 reason 兩個參數來響應給客戶端( 如上圖所示 )。
@ResponseStatus 也可以使用在方法上,這時,任何請求該目標方法的請求,都會響應異常給客戶端。
DefaultHandlerExceptionResolver
簡介
例如:出現 NoSuchRequestHandlingMethodException 異常時,經過 DefaultHandlerExceptionResolver 解析響應的頁面為:
SimpleMappingExceptionResolver
簡介
在配置文件中配置
<!--配置SimpleMappingExceptionResolver(簡單映射異常解析器)-->
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--配置SimpleMappingExceptionResolver的異常屬性exceptionAttribute的名稱-->
<property name="exceptionAttribute" value="ex"></property>
<!--配置異常類型以及跳轉的視圖,參數類型為 Properties的Map類型,因此可以有多個異常以及視圖-->
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">errorView/error</prop>
</props>
</property>
</bean>
注:當出現的異常在該配置中進行過配置,在異常發生時,則會跳轉到指定的視圖,且異常對象也會保存到 request 域中。
SpringMVC運行流程
文字說明
首先,客戶端發送一個請求,被 JavaWeb 應用程序的 web.xml 配置文件中的 DispatcherServlet 處理,在 SpringMVC 中,所有的請求,都通過該 Servlet 統一處理。
然后,檢查對於該請求,是否在 SpringMVC 中存在對應的請求映射,也就是 @RequestMapping。
若映射不存在:則檢查 SpringMVC 自己的配置文件中是否配置 < mvc:default-servlet-handler/ >
若沒有配置,則控制台輸出:No mapping found for HTTP request whith URI [/xx/xx] in DispatcherServlet (在DispatcherServlet中找不到HTTP請求和URI [/ xx / xx]的映射),同時,在視圖層面上,顯示 404 頁面。
若配置了,則直接通過查找目標資源,進行訪問,若沒有目標資源,則給出 404 頁面,但控制台沒有相關信息的輸出。
若映射存在,則由 HandlerMapping(處理程序映射) 獲取一個 HandlerExecutionChain(處理程序執行鏈) 對象,然后再獲取 HandlerAdapter(處理程序適配器),然后調用攔截器的 preHandler() 方法,然后調用目標方法得到 ModelAndView 對象,然后調用攔截器的 postHandler() 方法,這時,檢查調用目標方法執行的過程中,是否產生異常
若有異常,此時,不會執行 postHandler() 方法,且由 HandlerExceptionResolver(處理程序異常解析器) 組件處理異常,得到新的 ModelAndView 對象
若不存在異常,則 ModelAndView 不發生變化。
由 ViewResolver(視圖解析器) 解析 ModelAndView 對象,最終渲染視圖,得到實際的顯示在客戶端上的視圖,最后調用攔截器的 aftedrComplection() 方法
圖片演示

源碼級別簡單解析
如下

注:第 5 步,適配器執行目標方法后會獲得 MOdelAndView 對象。
Spring整合SpringMVC
是否需要在 web.xml 配置文件中配置啟動 Spring IOC 的 ContextLoaderLinstener (上下文加載監聽器)
需要,通常情況下,類似於數據源,事務,整合其他框架,都是放在 Spring 的配置文件中,而不是放在 SpringMVC 的配置文件中,實際上放入 Spring 配置文件對應 IOC 容器中的,還有 Service ,Dao 等。
不需要,都放在 SpringMVC 的配置文件中,也可以分多個Spring 的配置文件,使用 import 節點導入其他的配置文件,例如
<!--impoprt 可以通過 resource 屬性導入其他的外部配置文件 --> <import resource="spring_mvc-servlet.xml"></import>
綜合上述情況,也更建議將 Spring 與 SpringMVC 進行整合。
步驟:
1.添加 Spring 配置文件
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--開啟注解掃描-->
<context:component-scan base-package="SpringIntegrationSpringMVCTest"></context:component-scan>
<!--配置數據源,整合 其他框架,事務等-->
</beans>
2.在 web.xml 中配置啟動 Spring IOC 容器的 Listener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
問題:若 SpringMVC 和 Spring IOC 容器配置文件中,注解掃描的包有重合的部分,可能會導致有的 bean 被創建多次。
解決:
1.使 Spring IOC 容器掃描的包與 SpringMVC 掃描的包沒有重合的部分。
2.使用 exclude-filter 和 include-filter 子節點來規定只能掃描的注解,由此將兩個配置文件掃描的注解區別開來
配置文件如下:
SpringMVC
<!--開啟組件掃描 use-default-filters:配置掃描時,是否使用默認過濾器,值為布爾類型 --> <context:component-scan base-package="SpringIntegrationSpringMVCTest" use-default-filters="false"> <!--使用context:include-filter,配置掃描指定的注解--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
Spring IOC 容器
<!--開啟注解掃描--> <context:component-scan base-package="SpringIntegrationSpringMVCTest" use-default-filters="false"> <!--使用 context:exclude-filter 指定不掃描的注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
注:實際上,現階段,沒有遇到上述問題。
SpringMVC 與 Spring IOC 容器的關系
1.SpringMVC IOC 容器中的 bean 可以引用 Spring IOC 容器中的 bean,反之,則不行。
如圖:
二者存在一種父子關系
如圖:
即 WEB 容器層可以引用到業務層,而業務層則不能引用 WEB 容器層。
