springmvc學習筆記(全)

SpringMVC簡介

什麼是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視圖,渲染數據後最終響應給瀏覽器

什麼是SpringMVC

SpringMVC是Spring的一個後續產品,是Spring的一個子項目

SpringMVC 是 Spring 為表述層開發提供的一整套完備的解決方案。在表述層框架歷經 Strust、WebWork、Strust2 等諸多產品的歷代更迭之後,目前業界普遍選擇了 SpringMVC 作為 Java EE 項目表述層開發的首選方案

註:三層架構分為表述層(或表示層)、業務邏輯層、數據訪問層,表述層表示前台頁面和後台servlet

SpringMVC的特點

  • Spring 家族原生產品,與 IOC 容器等基礎設施無縫對接
  • 基於原生的Servlet,通過功能強大的前端控制器DispatcherServlet,對請求和響應進行統一處理
  • 表述層各細分領域需要解決的問題全方位覆蓋,提供全面解決方案
  • 程式碼清新簡潔,大幅度提升開發效率
  • 內部組件化程度高,可插拔式組件即插即用,想要什麼功能配置相應組件即可
  • 性能卓著,尤其適合現代大型、超大型互聯網項目要求

HelloWorld

Maven

打包方式: <packaging>war</packaging>

添加依賴:

<dependencies>
        <!-- SpringMVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!-- 日誌 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <!-- ServletAPI -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring5和Thymeleaf整合包 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
    </dependencies>

scope(依賴範圍):provided(已被提供),表示伺服器已經提供該依賴,所以此依賴不會被打包

配置web.xml

註冊SpringMVC的前端控制器DispatcherServlet

默認配置方式

此配置作用下,SpringMVC的配置文件默認位於WEB-INF下,默認名稱為<servlet-name>,例如,以下配置所對應SpringMVC的配置文件位於WEB-INF下,文件名為springMVC-servlet.xml

<!-- 配置SpringMVC的前端控制器,對瀏覽器發送的請求統一進行處理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        設置springMVC的核心控制器所能處理的請求的請求路徑
        /所匹配的請求可以是/login或.html或.js或.css方式的請求路徑
        但是/不能匹配.jsp請求路徑的請求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

擴展配置方式(推薦)

可通過init-param標籤設置SpringMVC配置文件的位置和名稱,通過load-on-startup標籤設置SpringMVC前端控制器DispatcherServlet的初始化時間

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="//xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//xmlns.jcp.org/xml/ns/javaee //xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 配置SpringMVC的前端控制器,對瀏覽器發送的請求統一進行處理 -->
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 通過初始化參數指定SpringMVC配置文件的位置和名稱 -->
        <init-param>
            <!-- contextConfigLocation為固定值 -->
            <param-name>contextConfigLocation</param-name>
            <!-- 使用classpath:表示從類路徑查找配置文件,例如maven工程中的src/main/resources -->
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--
             作為框架的核心組件,在啟動過程中有大量的初始化操作要做
            而這些操作放在第一次請求時才執行會嚴重影響訪問速度
            因此需要通過此標籤將啟動控制DispatcherServlet的初始化時間提前到伺服器啟動時
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <!--
            設置springMVC的核心控制器所能處理的請求的請求路徑
            /所匹配的請求可以是/login或.html或.js或.css方式的請求路徑
            但是/不能匹配.jsp請求路徑的請求
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

註:<url-pattern>標籤中使用//*的區別:

/所匹配的請求可以是.html或.js或.css方式的請求路徑,但是/不能匹配.jsp請求路徑的請求,

避免在訪問jsp頁面時,請求被DispatcherServlet處理而找不到相應的頁面。

/*則能夠匹配所有請求,例如在使用過濾器時,若需要對所有請求進行過濾,就需要使用/*的寫法

創建springMVC的配置文件

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xmlns:context="//www.springframework.org/schema/context"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd">

    <!--    配置包掃描-->
    <context:component-scan base-package="com.birdy"></context:component-scan>

    <!-- 配置Thymeleaf視圖解析器 -->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

                        <!-- 視圖前綴 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>

                        <!-- 視圖後綴 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

</beans>

頁面

前端頁面文件放置在路徑src/main/webapp/WEB-INF/templates/

<!--index.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
<h1>hello world!</h1>
</body>
</html>

創建請求控制器

由於前端控制器對瀏覽器發送的請求進行了統一的處理,但是不同的請求可能需要不同的處理過程,因此需要創建單獨處理請求的類,即請求控制器

請求控制器中處理請求的方法成為控制器方法

因為SpringMVC的控制器由一個POJO(普通的Java類)擔任,因此需要通過@Controller註解將其標識為一個控制層組件,交給Spring的IoC容器管理,此時SpringMVC才能夠識別控制器的存在

@Controller
public class HelloController {

    @RequestMapping("/")
    public String index(){
        //控制前將前端傳過來的路徑映射到指定文件
        
        return "index"; //將請求轉發到index.html文件
    }
}

註:之所以 return "index";就可以映射到index.html文件,是因為在springMVC.xml配置了視圖解析器處理請求的規則,即自動添加了前綴文件路徑和後綴文件類型

總結

瀏覽器發送請求,若請求地址符合前端控制器的url-pattern,該請求就會被前端控制器DispatcherServlet處理。前端控制器會讀取SpringMVC的核心配置文件,通過掃描組件找到控制器,將請求地址和控制器中@RequestMapping註解的value屬性值進行匹配,若匹配成功,該註解所標識的控制器方法就是處理請求的方法。處理請求的方法需要返回一個字元串類型的視圖名稱,該視圖名稱會被視圖解析器解析,加上前綴和後綴組成視圖的路徑,通過Thymeleaf對視圖進行渲染,最終轉發到視圖所對應頁面

@RequestMapping註解

作用:顧名思義,@RequestMapping`註解的作用就是將請求和處理請求的控制器方法關聯起來,建立映射關係。SpringMVC 接收到指定的請求,就會來找到在映射關係中對應的控制器方法來處理這個請求。

位置

  • @RequestMapping標識一個類:設置請求路徑的初始資訊,作為類里所有控制器方法映射路徑的的公共前綴部分
  • @RequestMapping標識一個方法:設置映射請求請求路徑的具體資訊
@Controller
@RequestMapping("/test")
public class RequestMappingController {

	//此時映射的請求路徑為:/test/testRequestMapping
    @RequestMapping("/testRequestMapping")
    public String testRequestMapping(){
        return "success";
    }
}

屬性

value

value屬性通過請求的請求地址匹配請求映射,@RequestMapping註解的value屬性是一個字元串類型的數組,表示該請求映射能夠匹配多個請求地址所對應的請求,必填

<a th:href="@{/testRequestMapping}">測試@RequestMapping的value屬性-->/testRequestMapping</a><br>
<a th:href="@{/test}">測試@RequestMapping的value屬性-->/test</a><br>
@RequestMapping( value = {"/testRequestMapping", "/test"})
public String testRequestMapping(){
    return "success";
}

method

  • @RequestMapping註解的method屬性通過定義請求方式(get或post)匹配請求映射
  • method屬性是一個RequestMethod類型的數組,表示該請求映射能夠匹配多種請求方式的請求

若一個請求的請求地址滿足value屬性,但是請求方式不滿足method屬性,則瀏覽器報錯405:Request method 'xxx' not supported

<a th:href="@{/test}">測試@RequestMapping的value屬性-->/test</a><br>
<form th:action="@{/test}" method="post">
    <input type="submit">
</form>
@RequestMapping(
        value = {"/testRequestMapping", "/test"},
        method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    return "success";
}

註:

對於處理指定請求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生註解

  • 處理get請求的映射–>@GetMapping
  • 處理post請求的映射–>@PostMapping
  • 處理put請求的映射–>@PutMapping
  • 處理delete請求的映射–>@DeleteMapping

常用的請求方式有get,post,put,delete,但是目前瀏覽器只支援get和post,若在form表單提交時,為method設置了其他請求方式的字元串(put或delete),則按照默認的請求方式get處理。

若要發送put和delete請求,則需要通過spring提供的過濾器HiddenHttpMethodFilter,在RESTful部分會講到

params(了解)

@RequestMapping註解的params屬性通過請求的請求參數匹配請求映射,@RequestMapping註解的params屬性是一個字元串類型的數組,可以通過四種表達式設置請求參數和請求映射的匹配關係

"param":要求請求映射所匹配的請求必須攜帶param請求參數
"!param":要求請求映射所匹配的請求必須不能攜帶param請求參數
"param=value":要求請求映射所匹配的請求必須攜帶param請求參數且param=value
"param!=value":要求請求映射所匹配的請求必須攜帶param請求參數但是param!=value

示例:

<a th:href="@{/test(username='admin',password=123456)">測試@RequestMapping的params屬性-->/test</a><br>
@RequestMapping(
        value = {"/testRequestMapping", "/test"}
        ,method = {RequestMethod.GET, RequestMethod.POST}
        ,params = {"username","password!=123456"}	//表示請求必須攜帶username屬性,值任意;必須攜													帶password屬性,且值必須為123456
)
public String testRequestMapping(){
    return "success";
}

註:

  • thymeleaf使用@{}填寫路徑,且自帶tomcat中設置的默認文本路徑
  • 使用()填寫請求參數

headers(了解)

@RequestMapping註解的headers屬性通過請求的請求頭資訊匹配請求映射。@RequestMapping註解的headers屬性是一個字元串類型的數組,可以通過四種表達式設置請求頭資訊和請求映射的匹配關係

"header":要求請求映射所匹配的請求必須攜帶header請求頭資訊
"!header":要求請求映射所匹配的請求必須不能攜帶header請求頭資訊
"header=value":要求請求映射所匹配的請求必須攜帶header請求頭資訊且header=value
"header!=value":要求請求映射所匹配的請求必須攜帶header請求頭資訊且header!=value

若當前請求滿足@RequestMapping註解的value和method屬性,但是不滿足headers屬性,此時頁面顯示404錯誤,即資源未找到

SpringMVC支援ant風格的路徑

:表示任意的單個字元

*:表示任意的0個或多個字元

**:表示任意的一層或多層目錄

注意:在使用**時,只能使用/**/xxx的方式

路徑中的佔位符(重點)

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

SpringMVC路徑中的佔位符常用於RESTful風格中,當請求路徑中將某些數據通過路徑的方式傳輸到伺服器中,就可以在@RequestMapping註解的value屬性中通過佔位符{xxx}表示傳輸的數據,然後通過@PathVariable註解,將佔位符所表示的數據賦值給控制器方法的形參

<a th:href="@{/testRest/1/admin}">測試路徑中的佔位符-->/testRest</a><br>
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id,
                       @PathVariable("username") String username){
    System.out.println("id:"+id+",username:"+username);
    return "success";
}
//最終輸出的內容為-->id:1,username:admin

SpringMVC獲取請求參數

通過ServletAPI獲取

HttpServletRequest作為控制器方法的形參,此時HttpServletRequest類型的參數就是封裝了當前請求報文的對象

<a th:href="@{/servlet(username='admin',password=123)}">測試servlet傳參</a>
@RequestMapping("/servlet")
	public String testParam(HttpServletRequest request){
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		System.out.println("username:"+username+",password:"+password);
		return "target";
	}
//列印結果:username:admin,password:123

通過SpringMVC控制器方法的形參獲取請求參數

在特定情況下才會使用原始Servlet傳參,SpringMVC自帶形參匹配功能方便傳參,在控制器方法的形參位置,設置和請求參數同名的形參,當瀏覽器發送請求,匹配到請求映射時,在DispatcherServlet中就會將請求參數賦值給相應的形參,推薦使用。

 <a th:href="@{/default(username='birdy',password=123)}">測試SringMVC形參方式</a>
@RequestMapping("/default")
	public String testDefault(String username, Integer password){
		System.out.println("username:"+username+",password:"+password);
		return "target";
	}
//列印結果:username:birdy,password:123

@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所指定的請求參數沒有傳輸或傳輸的值為空""時,則使用默認值為形參賦值

@RequestMapping("/default")
	public String testDefault(
        @RequestParam(value = "username", required = false) String username,
		@RequestParam(value = "password", required = false) String password
    )
    {
		System.out.println("username:"+username+",password:"+password);
		return "target";
	}
//列印結果:username:birdy,password:123

@RequestHeader

@RequestHeader是將請求頭資訊和控制器方法的形參創建映射關係

@RequestHeader註解一共有三個屬性:value、required、defaultValue,用法同@RequestParam

@RequestMapping("/header")
public String testHeader(@RequestHeader(value = "Host") String host,
                         @RequestHeader(value = "Accept-Encoding") String code){
    System.out.println(host+"**"+code);
    return "target";
}

@CookieValue

@CookieValue是將cookie數據和控制器方法的形參創建映射關係

@CookieValue註解一共有三個屬性:value、required、defaultValue,用法同@RequestParam

session依賴於cookie,cookie在第一次請求時由伺服器創建,通過響應頭返回給瀏覽器並保存在瀏覽器中,cookie默認在瀏覽器關閉後自動清除

解決獲取請求參數的亂碼問題

解決獲取請求參數的亂碼問題,可以使用SpringMVC提供的編碼過濾器CharacterEncodingFilter,需要在web.xml中進行註冊

<!--配置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>
    <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>

註:SpringMVC中處理編碼的過濾器一定要配置到其他過濾器之前,否則無效

域對象共享數據

servlet四種域對象

當前頁面 (pagecontext)

  • 生命周期:當對JSP的請求時開始,當響應結束時銷毀。
  • 作用範圍:整個JSP頁面,是四大作用域中最小的一個,即超過這個頁面就不能夠使用了。(所以使用pageContext對象向其它頁面傳遞參數是不可能的.)
  • 常用功能:
    • 獲取其它八大隱式對象,可以認為是一個入口對象。
    • 獲取其所有域中的數據
    • 跳轉到其他資源,其身上提供了forward和include方法,簡化重定向和轉發的操作。

一次請求(requset)

	設定的對象在一次請求中有效,一次請求簡單的理解為用戶點擊一次超鏈接,當用戶第二次點擊的時候瀏覽器又發送一個新的請求給伺服器,所以在一次點擊後,後台不管跳轉多少個servlet,jsp都算是一次請求,而後台在所跳轉的任何介面都能訪問設定的對象(如登錄介面向其他管理介面傳送登錄的用戶資訊等)
  • 生命周期:在service 方法調用前由伺服器創建,傳入service方法。整個請求結束,request生命結束。
  • 作用範圍:整個請求鏈(請求轉發也存在)。
  • 常用功能: 在整個請求鏈中共享數據。例如在Servlet 中處理好的數據交給Jsp顯示,此時參數就可以放置在Request域中帶過去

一次會話(session)

  • 生命周期:在第一次調用 request.getSession() 方法時,伺服器會檢查是否已經有對應的session,如果沒有就在記憶體 中創建一個session並返回。當一段時間內session沒有被使用(默認為30分鐘),則伺服器會銷毀該session。如果伺服器非正常關閉(強行關閉),沒有到期的session也會跟著銷毀。如果調用session提供的invalidate(),可以立即銷毀session。

    注意:伺服器正常關閉,再啟動,Session對象會進行鈍化和活化操作。同時如果伺服器鈍化的時間在session 默認銷毀時間之內,則活化後session還是存在的。否則Session不存在。如果JavaBean 數據在session鈍化時,沒有實現Serializable 則當Session活化時,會消失。

  • 作用範圍:一次會話,直到瀏覽器關閉或默認30分鐘沒有操作session銷毀。

  • 常用功能:保存用戶登錄資訊。

ServletContext(整個項目)

  • 生命周期:當伺服器啟動時該域對象,當伺服器關閉或Web應用被移除時,ServletContext對象跟著銷毀。

  • 作用範圍:整個Web應用。相當於web應用的全局變數(如統計網站的訪問率等)

  • 常用功能:在不同Servlet 之間轉發,讀取資源文件。

request域對象共享數據

使用原生ServletAPI

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
    request.setAttribute("ServletRequestScope", "hello,servletAPI");
    return "success";
}
<!--thymleaf: 從request域中獲取共享數據 -->
<p th:text="${ServletRequestScope}"></p>

ModelAndView

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    /**
     * ModelAndView有Model和View的功能
     * Model主要用於向請求域共享數據
     * View主要用於設置視圖,實現頁面跳轉
     */
    ModelAndView mav = new ModelAndView();
    //向請求域共享數據
    mav.addObject("ModelAndViewRequestScope", "hello,ModelAndView");
    //設置視圖,實現頁面跳轉
    mav.setViewName("success");
    return mav;
}

Model

@RequestMapping("/testModel")
public String testModel(Model model){
    model.addAttribute("ModelRequestScope", "hello,Model");
    return "success";
}

Map

@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    map.put("testScope", "hello,Map");
    return "success";
}

ModelMap

@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
    modelMap.addAttribute("testScope", "hello,ModelMap");
    return "success";
}

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 {}

session

@RequestMapping("/testSession")
public String testSession(HttpSession session){   
    session.setAttribute("testSessionScope", "hello,session");    
    return "success";
}
<!--thymleaf: 從session域中獲取共享數據 -->
<p th:text="${sesson.testSessionScope}"></p>

ServletContext(Application)域

@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
	ServletContext application = session.getServletContext();
    application.setAttribute("testApplicationScope", "hello,application");
    return "success";
}
<!--thymleaf: 從servlet域中獲取共享數據 -->
<p th:text="${application.testApplicationScope}"></p>

SpringMVC的視圖

SpringMVC中的視圖是View介面,視圖的作用渲染數據,將模型Model中的數據展示給用戶

SpringMVC視圖的種類很多,默認有轉發視圖和重定向視圖

當工程引入jstl的依賴,轉發視圖會自動轉換為JstlView

若使用的視圖技術為Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的視圖解析器,由此視圖解析器解析之後所得到的是ThymeleafView

ThymeleafView

當控制器方法中所設置的視圖名稱沒有任何前綴時,此時的視圖名稱會被SpringMVC配置文件中所配置的視圖解析器解析,視圖名稱拼接視圖前綴和視圖後綴所得到的最終路徑,會通過轉發的方式實現跳轉

@RequestMapping("/testHello")
public String testHello(){
    return "hello";
}

轉發視圖

SpringMVC中默認的轉發視圖是InternalResourceView

SpringMVC中創建轉發視圖的情況:

當控制器方法中所設置的視圖名稱以forward:為前綴時,創建InternalResourceView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析,而是會將前綴forward:去掉,剩餘部分作為最終路徑通過轉發的方式實現跳轉。

例如:

forward:/
forward:/employee
@RequestMapping("/testForward")
public String testForward(){
    return "forward:/testHello";
}

視圖控制器view-controller

重定向視圖

SpringMVC中默認的重定向視圖是RedirectView

當控制器方法中所設置的視圖名稱以redirect:為前綴時,創建RedirectView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析,而是會將前綴redirect:去掉,剩餘部分作為最終路徑通過重定向的方式實現跳轉

@RequestMapping("/testRedirect")
public String testRedirect(){
    return "redirect:/testHello";
}

視圖控制器view-controller

當控制器方法中,僅僅用來實現頁面跳轉,即只需要設置視圖名稱時,可以將處理器方法使用view-controller標籤進行表示,在spring配置文件中添加

<!--
	path:設置處理的請求地址
	view-name:設置請求地址所對應的視圖名稱
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--開啟mvc註解驅動-->
<mvc:annotation-driven />

此時就可以刪除控制器方法

  @RequestMapping("/")
    public String index(HttpServletRequest request){
        return "index";
    }

註:

  • 當SpringMVC中設置任何一個view-controller時,其他控制器中的請求映射將全部失效,此時需要在SpringMVC的核心配置文件中設置開啟mvc註解驅動的標籤:<mvc:annotation-driven />

Restful

RESTful簡介

REST:Representational State Transfer,表現層資源狀態轉移。

a>資源

資源是一種看待伺服器的方式,即,將伺服器看作是由很多離散的資源組成。每個資源是伺服器上一個可命名的抽象概念。因為資源是一個抽象的概念,所以它不僅僅能代表伺服器文件系統中的一個文件、資料庫中的一張表等等具體的東西,可以將資源設計的要多抽象有多抽象,只要想像力允許而且客戶端應用開發者能夠理解。與面向對象設計類似,資源是以名詞為核心來組織的,首先關注的是名詞。一個資源可以由一個或多個URI來標識。URI既是資源的名稱,也是資源在Web上的地址。對某個資源感興趣的客戶端應用,可以通過資源的URI與其進行交互。

b>資源的表述

資源的表述是一段對於資源在某個特定時刻的狀態的描述。可以在客戶端-伺服器端之間轉移(交換)。資源的表述可以有多種格式,例如HTML/XML/JSON/純文本/圖片/影片/音頻等等。資源的表述格式可以通過協商機制來確定。請求-響應方向的表述通常使用不同的格式。

c>狀態轉移

狀態轉移說的是:在客戶端和伺服器端之間轉移(transfer)代表資源狀態的表述。通過轉移和操作資源的表述,來間接實現操作資源的目的。

實現

具體說,就是 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請求方式

過濾器HiddenHttpMethodFilter

由於瀏覽器只支援發送get和post方式的請求,那麼該如何發送put和delete請求呢?

SpringMVC 提供了 HiddenHttpMethodFilter 幫助我們將 POST 請求轉換為 DELETE 或 PUT 請求

HiddenHttpMethodFilter處理put和delete請求的需要滿足條件:

  • 當前請求的請求方式必須為post
  • 當前請求必須傳輸請求參數_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中提供了兩個過濾器:CharacterEncodingFilterHiddenHttpMethodFilter

在web.xml中註冊時,必須先註冊CharacterEncodingFilter,再註冊HiddenHttpMethodFilter

原因:CharacterEncodingFilter 中通過 request.setCharacterEncoding(encoding) 方法設置字符集的,要求前面不能有任何獲取請求參數的操作。而 HiddenHttpMethodFilter 恰恰有一個後去請求參數_method當作真實請求方式的操作

RESTful案例

準備工作

和傳統 CRUD 一樣,實現對員工資訊的增刪改查。

搭建環境

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="//xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//xmlns.jcp.org/xml/ns/javaee //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>

    <!--配置處理請求方式put和delete的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的前端控制器DispatcherServlet-->
    <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>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

spring配置文件springMVC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
       xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
       xmlns:context="//www.springframework.org/schema/context"
       xmlns:mvc="//www.springframework.org/schema/mvc"
       xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd //www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd //www.springframework.org/schema/mvc //www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--掃描組件-->
    <context:component-scan base-package="com.birdy.rest"></context:component-scan>

    <!--配置Thymeleaf視圖解析器-->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">

                        <!-- 視圖前綴 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>

                        <!-- 視圖後綴 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--配置視圖控制器-->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    <mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>

    <!--開放對靜態資源的訪問-->
    <mvc:default-servlet-handler />

    <!--開啟mvc註解驅動-->
    <mvc:annotation-driven />

</beans>

實體類

package com.birdy.mvc.bean;

public class Employee {

   private Integer id;
   private String lastName;

   private String email;
   //1 male, 0 female
   private Integer gender;
   
   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }

   public String getLastName() {
      return lastName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(String email) {
      this.email = email;
   }

   public Integer getGender() {
      return gender;
   }

   public void setGender(Integer gender) {
      this.gender = gender;
   }

   public Employee(Integer id, String lastName, String email, Integer gender) {
      super();
      this.id = id;
      this.lastName = lastName;
      this.email = email;
      this.gender = gender;
   }

   public Employee() {
   }
}

準備dao模擬數據

package com.birdy.mvc.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.birdy.mvc.bean.Employee;
import org.springframework.stereotype.Repository;


@Repository
public class EmployeeDao {

   private static Map<Integer, Employee> employees = null;
   
   static{
      employees = new HashMap<Integer, Employee>();

      employees.put(1001, new Employee(1001, "E-AA", "[email protected]", 1));
      employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1));
      employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0));
      employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0));
      employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 1));
   }
   
   private static Integer initId = 1006;
   
   public void save(Employee employee){
      if(employee.getId() == null){
         employee.setId(initId++);
      }
      employees.put(employee.getId(), employee);
   }
   
   public Collection<Employee> getAll(){
      return employees.values();
   }
   
   public Employee get(Integer id){
      return employees.get(id);
   }
   
   public void delete(Integer id){
      employees.remove(id);
   }
}

功能清單

功能 URL 地址 請求方式
訪問首頁√ / GET
查詢全部數據√ /employee GET
刪除√ /employee/2 DELETE
跳轉到添加數據頁面√ /toAdd GET
執行保存√ /employee POST
跳轉到更新數據頁面√ /employee/2 GET
執行更新√ /employee PUT

具體功能:訪問首頁

配置view-controller

<mvc:view-controller path="/" view-name="index"/>

創建頁面

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8" >
    <title>Title</title>
</head>
<body>
<h1>首頁</h1>
<a th:href="@{/employee}">訪問員工資訊</a>
</body>
</html>

查詢所有員工數據

@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
    Collection<Employee> employeeList = employeeDao.getAll();
    model.addAttribute("employeeList", employeeList);
    return "employee_list";
}

創建employee_list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee Info</title>
    <script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>

    <table border="1" cellpadding="0" cellspacing="0" style="text-align: center;" id="dataTable">
        <tr>
            <th colspan="5">Employee Info</th>
        </tr>
        <tr>
            <th>id</th>
            <th>lastName</th>
            <th>email</th>
            <th>gender</th>
            <th>options(<a th:href="@{/toAdd}">add</a>)</th>
        </tr>
        <tr th:each="employee : ${employeeList}">
            <td th:text="${employee.id}"></td>
            <td th:text="${employee.lastName}"></td>
            <td th:text="${employee.email}"></td>
            <td th:text="${employee.gender}"></td>
            <td>
                <a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
                <a th:href="@{'/employee/'+${employee.id}}">update</a>
            </td>
        </tr>
    </table>
</body>
</html>

刪除

a>創建處理delete請求方式的表單

<!-- 作用:通過超鏈接控制表單的提交,將post請求轉換為delete請求 -->
<form id="delete_form" method="post">
    <!-- HiddenHttpMethodFilter要求:必須傳輸_method請求參數,並且值為最終的請求方式 -->
    <input type="hidden" name="_method" value="delete"/>
</form>

b>刪除超鏈接綁定點擊事件

引入vue.js

<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>

刪除超鏈接

<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>

通過vue處理點擊事件

<script type="text/javascript">
    var vue = new Vue({
        el:"#dataTable",
        methods:{
            //event表示當前事件
            deleteEmployee:function (event) {
                //通過id獲取表單標籤
                var delete_form = document.getElementById("delete_form");
                //將觸發事件的超鏈接的href屬性為表單的action屬性賦值
                delete_form.action = event.target.href;
                //提交表單
                delete_form.submit();
                //阻止超鏈接的默認跳轉行為
                event.preventDefault();
            }
        }
    });
</script>

控制器方法

@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
    employeeDao.delete(id);
    return "redirect:/employee";
}

跳轉到添加數據頁面

配置view-controller

<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>

b>創建employee_add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Add Employee</title>
</head>
<body>

<form th:action="@{/employee}" method="post">
    lastName:<input type="text" name="lastName"><br>
    email:<input type="text" name="email"><br>
    gender:<input type="radio" name="gender" value="1">male
    <input type="radio" name="gender" value="0">female<br>
    <input type="submit" value="add"><br>
</form>

</body>
</html>

具體功能:執行保存

a>控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

跳轉到更新數據頁面

a>修改超鏈接

<a th:href="@{'/employee/'+${employee.id}}">update</a>

b>控制器方法

@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
    Employee employee = employeeDao.get(id);
    model.addAttribute("employee", employee);
    return "employee_update";
}

c>創建employee_update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Update Employee</title>
</head>
<body>

<form th:action="@{/employee}" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="hidden" name="id" th:value="${employee.id}">
    lastName:<input type="text" name="lastName" th:value="${employee.lastName}"><br>
    email:<input type="text" name="email" th:value="${employee.email}"><br>
    <!--
        th:field="${employee.gender}"可用於單選框或複選框的回顯
        若單選框的value和employee.gender的值一致,則添加checked="checked"屬性
    -->
    gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
    <input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br>
    <input type="submit" value="update"><br>
</form>

</body>
</html>

執行更新

a>控制器方法

@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

HttpMessageConverter

HttpMessageConverter,報文資訊轉換器,將請求報文轉換為Java對象,或將Java對象轉換為響應報文

HttpMessageConverter提供了兩個註解和兩個類型:

  • @RequestBody
  • @ResponseBody
  • RequestEntity
  • ResponseEntity

@RequestBody

@RequestBody可以獲取請求體,需要在控制器方法設置一個形參,使用@RequestBody進行標識,當前請求的請求體就會為當前註解所標識的形參賦值

<form th:action="@{/testRequestBody}" method="post">
    用戶名:<input type="text" name="username"><br>
    密碼:<input type="password" name="password"><br>
    <input type="submit">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
    System.out.println("requestBody:"+requestBody);
    return "success";
}
//輸出結果:requestBody:username=admin&password=123456

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:"//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

@ResponseBody

@ResponseBody註解的作用是將controller的方法返回的對象通過適當的轉換器轉換為指定的格式之後,寫入到response對象的body區,通常用來返回JSON數據或者是XML。需要注意的呢,在使用此註解之後不會再走試圖處理器,而是直接將數據寫入到輸入流中,他的效果等同於通過response對象輸出指定格式的數據。

@RequestMapping("/login")
@ResponseBody
public User login(User user){
    return user;
}
	//User欄位:userName pwd
   // 那麼在前台接收到的數據為:'{"userName":"xxx","pwd":"xxx"}'

   // 效果等同於如下程式碼:
    @RequestMapping("/login")
    public void login(User user, HttpServletResponse response){
    response.getWriter.write(JSONObject.fromObject(user).toString());
}

@ResponseBody是作用在方法上的,@ResponseBody 表示該方法的返回結果直接寫入 HTTP response body 中,一般在非同步獲取數據時使用

@ResponseBody盡量不要用在get請求中

SpringMVC處理json

@ResponseBody處理json的步驟:

1、導入jackson的依賴

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

2、在SpringMVC的核心配置文件中開啟mvc的註解驅動,此時在HandlerAdaptor中會自動裝配一個消息轉換器:MappingJackson2HttpMessageConverter,可以將響應到瀏覽器的Java對象轉換為Json格式的字元串

<mvc:annotation-driven />

3、在處理器方法上使用@ResponseBody註解進行標識,將Java對象直接作為控制器方法的返回值返回,就會自動轉換為Json格式的字元串

@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
    return new User(1001,"admin","123456",23,"男");
}

瀏覽器的頁面中展示的結果:

{"id":1001,"username":"admin","password":"123456","age":23,"sex":"男"}

處理Ajax

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
</head>
<body>
   <div id="app">
       <a th:href="@{/testAjax}" @click="testAjax">測試ajax</a>
   </div>
    <script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
    <script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
    <script type="text/javascript">
        new Vue({
            el: "#app",
            methods: {
                testAjax(event){
                    axios({
                        method: "post",
                        url: event.target.href,
                        params: {
                            username: "admin",
                            password: "123456"
                        }
                    }).then((res)=>{
                        alert(res.data)
                    })
                    //阻止超鏈接默認跳轉行為
                    event.preventDefault();
                }
            }
        })
    </script>
</body>
</html>
	@PostMapping("/testAjax")
	@ResponseBody
	public String testAjax(){
		return "hello,ajax";
	}

文件上傳和下載

文件下載

使用ResponseEntity實現下載文件的功能

@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
    //獲取ServletContext對象
    ServletContext servletContext = session.getServletContext();
    //獲取伺服器中文件的真實路徑
    String realPath = servletContext.getRealPath("/static/img/1.jpg");
    //創建輸入流
    InputStream is = new FileInputStream(realPath);
    //創建位元組數組
    byte[] bytes = new byte[is.available()];
    //將流讀到位元組數組中
    is.read(bytes);
    //創建HttpHeaders對象設置響應頭資訊
    MultiValueMap<String, String> headers = new HttpHeaders();
    //設置要下載方式以及下載文件的名字
    headers.add("Content-Disposition", "attachment;filename=1.jpg");
    //設置響應狀態碼
    HttpStatus statusCode = HttpStatus.OK;
    //創建ResponseEntity對象
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,statusCode);
    //關閉輸入流
    is.close();
    return responseEntity;
}

文件上傳

文件上傳要求form表單的請求方式必須為post,並且添加屬性enctype="multipart/form-data"

SpringMVC中將上傳的文件封裝到MultipartFile對象中,通過此對象可以獲取文件相關資訊

1、添加依賴:

<!-- //mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

2、在SpringMVC的配置文件中添加配置:

<!--必須通過文件解析器的解析才能將文件轉換為MultipartFile對象,且必須配置id屬性-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

3、控制器方法:

@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
    //獲取上傳的文件的文件名
    String fileName = photo.getOriginalFilename();
    //處理文件重名問題
    String hzName = fileName.substring(fileName.lastIndexOf("."));
    fileName = UUID.randomUUID().toString() + hzName;
    //獲取伺服器中photo目錄的路徑
    ServletContext servletContext = session.getServletContext();
    String photoPath = servletContext.getRealPath("photo");
    File file = new File(photoPath);
    if(!file.exists()){
        file.mkdir();
    }
    String finalPath = photoPath + File.separator + fileName;
    //實現上傳功能
    photo.transferTo(new File(finalPath));
    return "success";
}

攔截器

SpringMVC中的攔截器用於攔截控制器方法的執行

攔截器的配置

SpringMVC中的攔截器需要實現HandlerInterceptor

@Component
public class FirstInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		System.out.println("FirstInterceptor-->preHandle");
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		System.out.println("FirstInterceptor-->postHandle");
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		System.out.println("FirstInterceptor-->afterCompletion");
	}
}

SpringMVC的攔截器必須在Spring的配置文件中進行配置:

攔截所有請求

<mvc:interceptors>
<!--按組件名配置:注意默認情況下容器中的組件自動變駝峰命名-->
    <ref bean="firstInterceptor"></ref>
</mvc:interceptors>

<mvc:interceptors>
   <!--按類名配置-->
<bean class="com.birdy.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>

自定義攔截請求

 <mvc:interceptor>
        <!--攔截所有請求-->
        <mvc:mapping path="/**"/>
        <!--配置不攔截的請求-->
        <mvc:exclude-mapping path="/"/>
        <ref bean="firstInterceptor"></ref>
    </mvc:interceptor>

攔截器的三個抽象方法

SpringMVC中的攔截器有三個抽象方法:

  • preHandle:控制器方法執行之前執行,其boolean類型的返回值表示是否攔截或放行
    • 返回true為放行,即調用控制器方法;
    • 返回false表示攔截,即不調用控制器方法
  • postHandle:控制器方法執行之後執行postHandle()
  • afterComplation:處理完視圖和模型數據,渲染視圖完畢之後執行afterComplation()

多個攔截器的執行順序

若每個攔截器的preHandle()都返回true。此時多個攔截器的執行順序和攔截器在SpringMVC的配置文件的配置順序有關:

  • preHandle()會按照配置的順序執行,而postHandle()和afterComplation()會按照配置的反序執行
  • 若某個攔截器的preHandle()返回了false,preHandle()返回false和它之前的攔截器的preHandle()都會執行,postHandle()都不執行,返回false的攔截器之前的攔截器的afterComplation()會執行

異常處理器

基於配置的異常處理

SpringMVC提供了一個處理控制器方法執行過程中所出現的異常的介面:HandlerExceptionResolver

HandlerExceptionResolver介面的實現類有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver

SpringMVC提供了自定義的異常處理器SimpleMappingExceptionResolver,使用方式:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
        	<!--
        		properties的鍵表示處理器方法執行過程中出現的異常
        		properties的值表示若出現指定異常時,設置一個新的視圖名稱,跳轉到指定頁面
        	-->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--
    	exceptionAttribute屬性設置一個屬性名,將出現的異常資訊在請求域中進行共享
    -->
    <property name="exceptionAttribute" value="ex"></property>
</bean>

基於註解的異常處理

//@ControllerAdvice將當前類標識為異常處理的組件
@ControllerAdvice
public class ExceptionController {

    //@ExceptionHandler用於設置所標識方法處理的異常
    @ExceptionHandler(ArithmeticException.class)
    //ex表示當前請求處理中出現的異常對象
    public String handleArithmeticException(Exception ex, Model model){
        //將將異常資訊放在域里共享
        model.addAttribute("ex", ex);
        return "error";
    }

}

測試

<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
</head>
<body>
   <a th:href="@{/testEx}">測試異常處理</a>
</body>
</html>

出現異常後跳轉的頁面

<!--error.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
出現錯誤
<!--/*@thymesVar id="ex" type="request"*/-->
<p th:text="${ex}"></p>
</body>
</html>

註解配置SpringMVC

創建初始化類,代替web.xml

在Servlet3.0環境中,容器會在類路徑中查找實現javax.servlet.ServletContainerInitializer介面的類,如果找到的話就用它來配置Servlet容器。

Spring提供了這個介面的實現,名為SpringServletContainerInitializer,這個類反過來又會查找實現WebApplicationInitializer的類並將配置的任務交給它們來完成。

Spring3.2引入了一個便利的WebApplicationInitializer基礎實現,名為AbstractAnnotationConfigDispatcherServletInitializer,當我們的類擴展了AbstractAnnotationConfigDispatcherServletInitializer並將其部署到Servlet3.0容器的時候,容器會自動發現它,並用它來配置Servlet上下文

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 指定spring的配置類
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
     * 指定SpringMVC的配置類
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    /**
     * 指定DispatcherServlet的映射規則,即url-pattern
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 添加過濾器
     * @return
     */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceRequestEncoding(true);
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
    }
}

Spring配置類

創建SpringConfig配置類,代替spring的配置文件

@Configuration
public class SpringConfig {
	//ssm整合之後,spring的配置資訊寫在此類中
}

SpringMVC的配置文件

創建WebConfig配置類,代替SpringMVC的配置文件

@Configuration
//掃描組件
@ComponentScan("com.atguigu.mvc.controller")
//開啟MVC註解驅動
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    //使用默認的servlet處理靜態資源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //配置文件上傳解析器
    @Bean
    public CommonsMultipartResolver multipartResolver(){
        return new CommonsMultipartResolver();
    }

    //配置攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        registry.addInterceptor(TestInterceptor).addPathPatterns("/**");
    }
    
    //配置視圖控制
   
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }
    
    //配置異常映射
    @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);
    }

    //配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一個ServletContext作為構造參數,可通過WebApplicationContext 的方法獲得
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
                webApplicationContext.getServletContext());
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    //生成模板引擎並為模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    //生成視圖解析器並未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

}

攔截器

public class TestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("TestInterceptor-->preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}