SpringMVC學(xué)習(xí)筆記

  • 1斜脂、什么是MVC
  • 2抓艳、什么是SpringMVC
  • 3、SpringMVC的特點
  • 1帚戳、開發(fā)環(huán)境
  • 2玷或、創(chuàng)建maven工程
  • 3、配置web.xml
  • 4片任、創(chuàng)建請求控制器
  • 5偏友、創(chuàng)建springMVC的配置文件
  • 6、測試HelloWorld
  • 7蚂踊、總結(jié)
  • 1约谈、@RequestMapping注解的功能
  • 2、@RequestMapping注解的位置
  • 3犁钟、@RequestMapping注解的value屬性
  • 4棱诱、@RequestMapping注解的method屬性
  • 5、@RequestMapping注解的params屬性(了解)
  • 6涝动、@RequestMapping注解的headers屬性(了解)
  • 7迈勋、SpringMVC支持ant風(fēng)格的路徑
  • 8、SpringMVC支持路徑中的占位符(重點)
  • 1醋粟、通過ServletAPI獲取
  • 2靡菇、通過控制器方法的形參獲取請求參數(shù)
  • 3、@RequestParam
  • 4米愿、@RequestHeader
  • 5厦凤、@CookieValue
  • 6、通過POJO獲取請求參數(shù)
  • 7育苟、解決獲取請求參數(shù)的亂碼問題
  • 1较鼓、使用ServletAPI向request域?qū)ο蠊蚕頂?shù)據(jù)
  • 2、使用ModelAndView向request域?qū)ο蠊蚕頂?shù)據(jù)
  • 3违柏、使用Model向request域?qū)ο蠊蚕頂?shù)據(jù)
  • 4博烂、使用map向request域?qū)ο蠊蚕頂?shù)據(jù)
  • 5、使用ModelMap向request域?qū)ο蠊蚕頂?shù)據(jù)
  • 6漱竖、Model禽篱、ModelMap、Map的關(guān)系
  • 7馍惹、向session域共享數(shù)據(jù)
  • 8躺率、向application域共享數(shù)據(jù)
  • 1玛界、ThymeleafView
  • 2、轉(zhuǎn)發(fā)視圖
  • 3悼吱、重定向視圖
  • 4脚仔、視圖控制器view-controller
  • 1、RESTful簡介
  • 2舆绎、RESTful的實現(xiàn)
  • 3鲤脏、HiddenHttpMethodFilter
  • 1、準(zhǔn)備工作
  • 2吕朵、功能清單
  • 3猎醇、具體功能:訪問首頁
  • 4、具體功能:查詢所有員工數(shù)據(jù)
  • 5努溃、具體功能:刪除
  • 6硫嘶、具體功能:跳轉(zhuǎn)到添加數(shù)據(jù)頁面
  • 7、具體功能:執(zhí)行保存
  • 8梧税、具體功能:跳轉(zhuǎn)到更新數(shù)據(jù)頁面
  • 9沦疾、具體功能:執(zhí)行更新
  • 1、@RequestBody
  • 2第队、RequestEntity
  • 3哮塞、@ResponseBody
  • 4、SpringMVC處理json
  • 5凳谦、SpringMVC處理ajax
  • 6忆畅、@RestController注解
  • 7、ResponseEntity
  • 1尸执、文件下載
  • 2家凯、文件上傳
  • 1、攔截器的配置
  • 2如失、攔截器的三個抽象方法
  • 3绊诲、多個攔截器的執(zhí)行順序
  • 1、基于配置的異常處理
  • 2褪贵、基于注解的異常處理
  • 1掂之、創(chuàng)建初始化類,代替web.xml
  • 2竭鞍、創(chuàng)建SpringConfig配置類板惑,代替spring的配置文件
  • 3愕难、創(chuàng)建WebConfig配置類掰茶,代替SpringMVC的配置文件
  • 4株茶、測試功能
  • 1、SpringMVC常用組件
  • 2晒夹、DispatcherServlet初始化過程
  • 3裆馒、DispatcherServlet調(diào)用組件處理請求
  • 4、SpringMVC的執(zhí)行流程

一丐怯、SpringMVC簡介

1喷好、什么是MVC

MVC是一種軟件架構(gòu)的思想,將軟件按照模型读跷、視圖梗搅、控制器來劃分

M:Model,模型層效览,指工程中的JavaBean无切,作用是處理數(shù)據(jù)

JavaBean分為兩類:

  • 一類稱為實體類Bean:專門存儲業(yè)務(wù)數(shù)據(jù)的,如 Student丐枉、User 等
  • 一類稱為業(yè)務(wù)處理 Bean:指 Service 或 Dao 對象哆键,專門用于處理業(yè)務(wù)邏輯和數(shù)據(jù)訪問。

V:View瘦锹,視圖層籍嘹,指工程中的html或jsp等頁面,作用是與用戶進(jìn)行交互弯院,展示數(shù)據(jù)

C:Controller辱士,控制層,指工程中的servlet听绳,作用是接收請求和響應(yīng)瀏覽器

MVC的工作流程:
用戶通過視圖層發(fā)送請求到服務(wù)器识补,在服務(wù)器中請求被Controller接收,Controller調(diào)用相應(yīng)的Model層處理請求辫红,處理完畢將結(jié)果返回到Controller凭涂,Controller再根據(jù)請求處理的結(jié)果找到相應(yīng)的View視圖,渲染數(shù)據(jù)后最終響應(yīng)給瀏覽器

2贴妻、什么是SpringMVC

SpringMVC是Spring的一個后續(xù)產(chǎn)品切油,是Spring的一個子項目

SpringMVC 是 Spring 為表述層開發(fā)提供的一整套完備的解決方案。在表述層框架歷經(jīng) Strust名惩、WebWork澎胡、Strust2 等諸多產(chǎn)品的歷代更迭之后,目前業(yè)界普遍選擇了 SpringMVC 作為 Java EE 項目表述層開發(fā)的首選方案娩鹉。

注:三層架構(gòu)分為表述層(或表示層)攻谁、業(yè)務(wù)邏輯層、數(shù)據(jù)訪問層弯予,表述層表示前臺頁面和后臺servlet

3戚宦、SpringMVC的特點

  • Spring 家族原生產(chǎn)品,與 IOC 容器等基礎(chǔ)設(shè)施無縫對接
  • 基于原生的Servlet锈嫩,通過了功能強(qiáng)大的前端控制器DispatcherServlet受楼,對請求和響應(yīng)進(jìn)行統(tǒng)一處理
  • 表述層各細(xì)分領(lǐng)域需要解決的問題全方位覆蓋垦搬,提供全面解決方案
  • 代碼清新簡潔,大幅度提升開發(fā)效率
  • 內(nèi)部組件化程度高艳汽,可插拔式組件即插即用猴贰,想要什么功能配置相應(yīng)組件即可
  • 性能卓著,尤其適合現(xiàn)代大型河狐、超大型互聯(lián)網(wǎng)項目要求

二米绕、HelloWorld

1、開發(fā)環(huán)境

IDE:idea 2019.2

構(gòu)建工具:maven3.5.4

服務(wù)器:tomcat7

Spring版本:5.3.1

2馋艺、創(chuàng)建maven工程

a>添加web模塊
b>打包方式:war
c>引入依賴
<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>

注:由于 Maven 的傳遞性义郑,我們不必將所有需要的包全部配置依賴,而是配置最頂端的依賴丈钙,其他靠傳遞性導(dǎo)入非驮。

3、配置web.xml

注冊SpringMVC的前端控制器DispatcherServlet

a>默認(rèn)配置方式

此配置作用下雏赦,SpringMVC的配置文件默認(rèn)位于WEB-INF下劫笙,默認(rèn)名稱為<servlet-name>-servlet.xml,例如星岗,以下配置所對應(yīng)SpringMVC的配置文件位于WEB-INF下填大,文件名為springMVC-servlet.xml

<!-- 配置SpringMVC的前端控制器,對瀏覽器發(fā)送的請求統(tǒng)一進(jìn)行處理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        設(shè)置springMVC的核心控制器所能處理的請求的請求路徑
        /所匹配的請求可以是/login或.html或.js或.css方式的請求路徑
        但是/不能匹配.jsp請求路徑的請求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>
b>擴(kuò)展配置方式

可通過init-param標(biāo)簽設(shè)置SpringMVC配置文件的位置和名稱俏橘,通過load-on-startup標(biāo)簽設(shè)置SpringMVC前端控制器DispatcherServlet的初始化時間

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

注:

<url-pattern>標(biāo)簽中使用/和/*的區(qū)別:

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

因此就可以避免在訪問jsp頁面時,該請求被DispatcherServlet處理召耘,從而找不到相應(yīng)的頁面

/*則能夠匹配所有請求百炬,例如在使用過濾器時,若需要對所有請求進(jìn)行過濾污它,就需要使用/*的寫法

4剖踊、創(chuàng)建請求控制器

由于前端控制器對瀏覽器發(fā)送的請求進(jìn)行了統(tǒng)一的處理,但是具體的請求有不同的處理過程衫贬,因此需要創(chuàng)建處理具體請求的類德澈,即請求控制器

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

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

@Controller
public class HelloController {
    
}

5、創(chuàng)建springMVC的配置文件

<!-- 自動掃描包 -->
<context:component-scan base-package="com.atguigu.mvc.controller"/>

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

<!-- 
   處理靜態(tài)資源缝呕,例如html澳窑、js、css供常、jpg
  若只設(shè)置該標(biāo)簽摊聋,則只能訪問靜態(tài)資源,其他請求則無法訪問
  此時必須設(shè)置<mvc:annotation-driven/>解決問題
 -->
<mvc:default-servlet-handler/>

<!-- 開啟mvc注解驅(qū)動 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 處理響應(yīng)中文內(nèi)容亂碼 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html</value>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

6栈暇、測試HelloWorld

a>實現(xiàn)對首頁的訪問

在請求控制器中創(chuàng)建處理請求的方法

// @RequestMapping注解:處理請求和控制器方法之間的映射關(guān)系
// @RequestMapping注解的value屬性可以通過請求地址匹配請求麻裁,/表示的當(dāng)前工程的上下文路徑
// localhost:8080/springMVC/
@RequestMapping("/")
public String index() {
    //設(shè)置視圖名稱
    return "index";
}
b>通過超鏈接跳轉(zhuǎn)到指定頁面

在主頁index.html中設(shè)置超鏈接

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <h1>首頁</h1>
    <a th:href="@{/hello}">HelloWorld</a><br/>
</body>
</html>

在請求控制器中創(chuàng)建處理請求的方法

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

7、總結(jié)

瀏覽器發(fā)送請求源祈,若請求地址符合前端控制器的url-pattern煎源,該請求就會被前端控制器DispatcherServlet處理。前端控制器會讀取SpringMVC的核心配置文件香缺,通過掃描組件找到控制器手销,將請求地址和控制器中@RequestMapping注解的value屬性值進(jìn)行匹配,若匹配成功图张,該注解所標(biāo)識的控制器方法就是處理請求的方法锋拖。處理請求的方法需要返回一個字符串類型的視圖名稱,該視圖名稱會被視圖解析器解析祸轮,加上前綴和后綴組成視圖的路徑兽埃,通過Thymeleaf對視圖進(jìn)行渲染,最終轉(zhuǎn)發(fā)到視圖所對應(yīng)頁面

三适袜、@RequestMapping注解

1柄错、@RequestMapping注解的功能

從注解名稱上我們可以看到,@RequestMapping注解的作用就是將請求和處理請求的控制器方法關(guān)聯(lián)起來苦酱,建立映射關(guān)系售貌。

SpringMVC 接收到指定的請求,就會來找到在映射關(guān)系中對應(yīng)的控制器方法來處理這個請求疫萤。

2趁矾、@RequestMapping注解的位置

@RequestMapping標(biāo)識一個類:設(shè)置映射請求的請求路徑的初始信息

@RequestMapping標(biāo)識一個方法:設(shè)置映射請求請求路徑的具體信息

@Controller
@RequestMapping("/test")
public class RequestMappingController {

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

}

3、@RequestMapping注解的value屬性

@RequestMapping注解的value屬性通過請求的請求地址匹配請求映射

@RequestMapping注解的value屬性是一個字符串類型的數(shù)組给僵,表示該請求映射能夠匹配多個請求地址所對應(yīng)的請求

@RequestMapping注解的value屬性必須設(shè)置毫捣,至少通過請求地址匹配請求映射

<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";
}

4、@RequestMapping注解的method屬性

@RequestMapping注解的method屬性通過請求的請求方式(get或post)匹配請求映射

@RequestMapping注解的method屬性是一個RequestMethod類型的數(shù)組帝际,表示該請求映射能夠匹配多種請求方式的請求

若當(dāng)前請求的請求地址滿足請求映射的value屬性蔓同,但是請求方式不滿足method屬性,則瀏覽器報錯405:Request method 'POST' 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";
}

注:

1蹲诀、對于處理指定請求方式的控制器方法斑粱,SpringMVC中提供了@RequestMapping的派生注解

處理get請求的映射-->@GetMapping

處理post請求的映射-->@PostMapping

處理put請求的映射-->@PutMapping

處理delete請求的映射-->@DeleteMapping

2、常用的請求方式有g(shù)et脯爪,post则北,put矿微,delete

但是目前瀏覽器只支持get和post,若在form表單提交時尚揣,為method設(shè)置了其他請求方式的字符串(put或delete)涌矢,則按照默認(rèn)的請求方式get處理

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

5娜庇、@RequestMapping注解的params屬性(了解)

@RequestMapping注解的params屬性通過請求的請求參數(shù)匹配請求映射

@RequestMapping注解的params屬性是一個字符串類型的數(shù)組,可以通過四種表達(dá)式設(shè)置請求參數(shù)和請求映射的匹配關(guān)系

"param":要求請求映射所匹配的請求必須攜帶param請求參數(shù)

"!param":要求請求映射所匹配的請求必須不能攜帶param請求參數(shù)

"param=value":要求請求映射所匹配的請求必須攜帶param請求參數(shù)且param=value

"param!=value":要求請求映射所匹配的請求必須攜帶param請求參數(shù)但是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"}
)
public String testRequestMapping(){
    return "success";
}

注:

若當(dāng)前請求滿足@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屬性是一個字符串類型的數(shù)組藕溅,可以通過四種表達(dá)式設(shè)置請求頭信息和請求映射的匹配關(guān)系

"header":要求請求映射所匹配的請求必須攜帶header請求頭信息

"!header":要求請求映射所匹配的請求必須不能攜帶header請求頭信息

"header=value":要求請求映射所匹配的請求必須攜帶header請求頭信息且header=value

"header!=value":要求請求映射所匹配的請求必須攜帶header請求頭信息且header!=value

若當(dāng)前請求滿足@RequestMapping注解的value和method屬性匕得,但是不滿足headers屬性,此時頁面顯示404錯誤巾表,即資源未找到

7耗跛、SpringMVC支持ant風(fēng)格的路徑

?:表示任意的單個字符

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

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

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

8调塌、SpringMVC支持路徑中的占位符(重點)

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

SpringMVC路徑中的占位符常用于RESTful風(fēng)格中,當(dāng)請求路徑中將某些數(shù)據(jù)通過路徑的方式傳輸?shù)椒?wù)器中惠猿,就可以在相應(yīng)的@RequestMapping注解的value屬性中通過占位符{xxx}表示傳輸?shù)臄?shù)據(jù)羔砾,在通過@PathVariable注解,將占位符所表示的數(shù)據(jù)賦值給控制器方法的形參

<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";
}
//最終輸出的內(nèi)容為-->id:1,username:admin

四偶妖、SpringMVC獲取請求參數(shù)

1姜凄、通過ServletAPI獲取

將HttpServletRequest作為控制器方法的形參,此時HttpServletRequest類型的參數(shù)表示封裝了當(dāng)前請求的請求報文的對象

@RequestMapping("/testParam")
public String testParam(HttpServletRequest request){
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println("username:"+username+",password:"+password);
    return "success";
}

2趾访、通過控制器方法的形參獲取請求參數(shù)

在控制器方法的形參位置态秧,設(shè)置和請求參數(shù)同名的形參,當(dāng)瀏覽器發(fā)送請求扼鞋,匹配到請求映射時申鱼,在DispatcherServlet中就會將請求參數(shù)賦值給相應(yīng)的形參

<a th:href="@{/testParam(username='admin',password=123456)}">測試獲取請求參數(shù)-->/testParam</a><br>
@RequestMapping("/testParam")
public String testParam(String username, String password){
    System.out.println("username:"+username+",password:"+password);
    return "success";
}

注:

若請求所傳輸?shù)恼埱髤?shù)中有多個同名的請求參數(shù),此時可以在控制器方法的形參中設(shè)置字符串?dāng)?shù)組或者字符串類型的形參接收此請求參數(shù)

若使用字符串?dāng)?shù)組類型的形參云头,此參數(shù)的數(shù)組中包含了每一個數(shù)據(jù)

若使用字符串類型的形參捐友,此參數(shù)的值為每個數(shù)據(jù)中間使用逗號拼接的結(jié)果

3、@RequestParam

@RequestParam是將請求參數(shù)和控制器方法的形參創(chuàng)建映射關(guān)系

@RequestParam注解一共有三個屬性:

value:指定為形參賦值的請求參數(shù)的參數(shù)名

required:設(shè)置是否必須傳輸此請求參數(shù)溃槐,默認(rèn)值為true

若設(shè)置為true時匣砖,則當(dāng)前請求必須傳輸value所指定的請求參數(shù),若沒有傳輸該請求參數(shù),且沒有設(shè)置defaultValue屬性猴鲫,則頁面報錯400:Required String parameter 'xxx' is not present对人;若設(shè)置為false,則當(dāng)前請求不是必須傳輸value所指定的請求參數(shù)拂共,若沒有傳輸牺弄,則注解所標(biāo)識的形參的值為null

defaultValue:不管required屬性值為true或false,當(dāng)value所指定的請求參數(shù)沒有傳輸或傳輸?shù)闹禐?"時匣缘,則使用默認(rèn)值為形參賦值

4猖闪、@RequestHeader

@RequestHeader是將請求頭信息和控制器方法的形參創(chuàng)建映射關(guān)系

@RequestHeader注解一共有三個屬性:value鲜棠、required肌厨、defaultValue,用法同@RequestParam

5豁陆、@CookieValue

@CookieValue是將cookie數(shù)據(jù)和控制器方法的形參創(chuàng)建映射關(guān)系

@CookieValue注解一共有三個屬性:value柑爸、required、defaultValue盒音,用法同@RequestParam

6表鳍、通過POJO獲取請求參數(shù)

可以在控制器方法的形參位置設(shè)置一個實體類類型的形參,此時若瀏覽器傳輸?shù)恼埱髤?shù)的參數(shù)名和實體類中的屬性名一致祥诽,那么請求參數(shù)就會為此屬性賦值

<form th:action="@{/testpojo}" method="post">
    用戶名:<input type="text" name="username"><br>
    密碼:<input type="password" name="password"><br>
    性別:<input type="radio" name="sex" value="男">男<input type="radio" name="sex" value="女">女<br>
    年齡:<input type="text" name="age"><br>
    郵箱:<input type="text" name="email"><br>
    <input type="submit">
</form>
@RequestMapping("/testpojo")
public String testPOJO(User user){
    System.out.println(user);
    return "success";
}
//最終結(jié)果-->User{id=null, username='張三', password='123', age=23, sex='男', email='123@qq.com'}

7譬圣、解決獲取請求參數(shù)的亂碼問題

解決獲取請求參數(shù)的亂碼問題,可以使用SpringMVC提供的編碼過濾器CharacterEncodingFilter雄坪,但是必須在web.xml中進(jìn)行注冊

<!--配置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中處理編碼的過濾器一定要配置到其他過濾器之前厘熟,否則無效

五、域?qū)ο蠊蚕頂?shù)據(jù)

1维哈、使用ServletAPI向request域?qū)ο蠊蚕頂?shù)據(jù)

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
    request.setAttribute("testScope", "hello,servletAPI");
    return "success";
}

2绳姨、使用ModelAndView向request域?qū)ο蠊蚕頂?shù)據(jù)

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    /**
     * ModelAndView有Model和View的功能
     * Model主要用于向請求域共享數(shù)據(jù)
     * View主要用于設(shè)置視圖,實現(xiàn)頁面跳轉(zhuǎn)
     */
    ModelAndView mav = new ModelAndView();
    //向請求域共享數(shù)據(jù)
    mav.addObject("testScope", "hello,ModelAndView");
    //設(shè)置視圖阔挠,實現(xiàn)頁面跳轉(zhuǎn)
    mav.setViewName("success");
    return mav;
}

3飘庄、使用Model向request域?qū)ο蠊蚕頂?shù)據(jù)

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

4、使用map向request域?qū)ο蠊蚕頂?shù)據(jù)

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

5购撼、使用ModelMap向request域?qū)ο蠊蚕頂?shù)據(jù)

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

6跪削、Model、ModelMap迂求、Map的關(guān)系

Model切揭、ModelMap、Map類型的參數(shù)其實本質(zhì)上都是 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域共享數(shù)據(jù)

@RequestMapping("/testSession")
public String testSession(HttpSession session){
    session.setAttribute("testSessionScope", "hello,session");
    return "success";
}

8廓旬、向application域共享數(shù)據(jù)

@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
    ServletContext application = session.getServletContext();
    application.setAttribute("testApplicationScope", "hello,application");
    return "success";
}

六、SpringMVC的視圖

SpringMVC中的視圖是View接口,視圖的作用渲染數(shù)據(jù)孕豹,將模型Model中的數(shù)據(jù)展示給用戶

SpringMVC視圖的種類很多涩盾,默認(rèn)有轉(zhuǎn)發(fā)視圖和重定向視圖

當(dāng)工程引入jstl的依賴,轉(zhuǎn)發(fā)視圖會自動轉(zhuǎn)換為JstlView

若使用的視圖技術(shù)為Thymeleaf励背,在SpringMVC的配置文件中配置了Thymeleaf的視圖解析器春霍,由此視圖解析器解析之后所得到的是ThymeleafView

1、ThymeleafView

當(dāng)控制器方法中所設(shè)置的視圖名稱沒有任何前綴時叶眉,此時的視圖名稱會被SpringMVC配置文件中所配置的視圖解析器解析址儒,視圖名稱拼接視圖前綴和視圖后綴所得到的最終路徑,會通過轉(zhuǎn)發(fā)的方式實現(xiàn)跳轉(zhuǎn)

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

2衅疙、轉(zhuǎn)發(fā)視圖

SpringMVC中默認(rèn)的轉(zhuǎn)發(fā)視圖是InternalResourceView

SpringMVC中創(chuàng)建轉(zhuǎn)發(fā)視圖的情況:

當(dāng)控制器方法中所設(shè)置的視圖名稱以"forward:"為前綴時莲趣,創(chuàng)建InternalResourceView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析饱溢,而是會將前綴"forward:"去掉喧伞,剩余部分作為最終路徑通過轉(zhuǎn)發(fā)的方式實現(xiàn)跳轉(zhuǎn)

例如"forward:/","forward:/employee"

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

3绩郎、重定向視圖

SpringMVC中默認(rèn)的重定向視圖是RedirectView

當(dāng)控制器方法中所設(shè)置的視圖名稱以"redirect:"為前綴時潘鲫,創(chuàng)建RedirectView視圖,此時的視圖名稱不會被SpringMVC配置文件中所配置的視圖解析器解析肋杖,而是會將前綴"redirect:"去掉溉仑,剩余部分作為最終路徑通過重定向的方式實現(xiàn)跳轉(zhuǎn)

例如"redirect:/","redirect:/employee"

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

注:

重定向視圖在解析時状植,會先將redirect:前綴去掉浊竟,然后會判斷剩余部分是否以/開頭,若是則會自動拼接上下文路徑

4浅萧、視圖控制器view-controller

當(dāng)控制器方法中逐沙,僅僅用來實現(xiàn)頁面跳轉(zhuǎn),即只需要設(shè)置視圖名稱時洼畅,可以將處理器方法使用view-controller標(biāo)簽進(jìn)行表示

<!--
    path:設(shè)置處理的請求地址
    view-name:設(shè)置請求地址所對應(yīng)的視圖名稱
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>

注:

當(dāng)SpringMVC中設(shè)置任何一個view-controller時吩案,其他控制器中的請求映射將全部失效,此時需要在SpringMVC的核心配置文件中設(shè)置開啟mvc注解驅(qū)動的標(biāo)簽:

<mvc:annotation-driven />

七帝簇、RESTful

1徘郭、RESTful簡介

REST:Representational State Transfer,表現(xiàn)層資源狀態(tài)轉(zhuǎn)移丧肴。

a>資源

資源是一種看待服務(wù)器的方式残揉,即,將服務(wù)器看作是由很多離散的資源組成芋浮。每個資源是服務(wù)器上一個可命名的抽象概念抱环。因為資源是一個抽象的概念,所以它不僅僅能代表服務(wù)器文件系統(tǒng)中的一個文件、數(shù)據(jù)庫中的一張表等等具體的東西镇草,可以將資源設(shè)計的要多抽象有多抽象眶痰,只要想象力允許而且客戶端應(yīng)用開發(fā)者能夠理解。與面向?qū)ο笤O(shè)計類似梯啤,資源是以名詞為核心來組織的希痴,首先關(guān)注的是名詞州胳。一個資源可以由一個或多個URI來標(biāo)識。URI既是資源的名稱践美,也是資源在Web上的地址宰缤。對某個資源感興趣的客戶端應(yīng)用猖凛,可以通過資源的URI與其進(jìn)行交互毒坛。

b>資源的表述

資源的表述是一段對于資源在某個特定時刻的狀態(tài)的描述蚪缀。可以在客戶端-服務(wù)器端之間轉(zhuǎn)移(交換)杭棵。資源的表述可以有多種格式婚惫,例如HTML/XML/JSON/純文本/圖片/視頻/音頻等等氛赐。資源的表述格式可以通過協(xié)商機(jī)制來確定魂爪。請求-響應(yīng)方向的表述通常使用不同的格式。

c>狀態(tài)轉(zhuǎn)移

狀態(tài)轉(zhuǎn)移說的是:在客戶端和服務(wù)器端之間轉(zhuǎn)移(transfer)代表資源狀態(tài)的表述艰管。通過轉(zhuǎn)移和操作資源的表述滓侍,來間接實現(xiàn)操作資源的目的。

2牲芋、RESTful的實現(xiàn)

具體說撩笆,就是 HTTP 協(xié)議里面,四個表示操作方式的動詞:GET缸浦、POST夕冲、PUT、DELETE裂逐。

它們分別對應(yīng)四種基本操作:GET 用來獲取資源歹鱼,POST 用來新建資源,PUT 用來更新資源卜高,DELETE 用來刪除資源弥姻。

REST 風(fēng)格提倡 URL 地址使用統(tǒng)一的風(fēng)格設(shè)計,從前到后各個單詞使用斜杠分開掺涛,不使用問號鍵值對方式攜帶請求參數(shù)庭敦,而是將要發(fā)送給服務(wù)器的數(shù)據(jù)作為 URL 地址的一部分,以保證整體風(fēng)格的一致性薪缆。

操作 傳統(tǒng)方式 REST風(fēng)格
查詢操作 getUserById?id=1 user/1-->get請求方式
保存操作 saveUser user-->post請求方式
刪除操作 deleteUser?id=1 user/1-->delete請求方式
更新操作 updateUser user-->put請求方式

3秧廉、HiddenHttpMethodFilter

由于瀏覽器只支持發(fā)送get和post方式的請求,那么該如何發(fā)送put和delete請求呢?

SpringMVC 提供了 HiddenHttpMethodFilter 幫助我們將 POST 請求轉(zhuǎn)換為 DELETE 或 PUT 請求

HiddenHttpMethodFilter 處理put和delete請求的條件:

a>當(dāng)前請求的請求方式必須為post

b>當(dāng)前請求必須傳輸請求參數(shù)_method

滿足以上條件疼电,HiddenHttpMethodFilter 過濾器就會將當(dāng)前請求的請求方式轉(zhuǎn)換為請求參數(shù)_method的值赔癌,因此請求參數(shù)_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) 方法設(shè)置字符集的

  • request.setCharacterEncoding(encoding) 方法要求前面不能有任何獲取請求參數(shù)的操作

  • 而 HiddenHttpMethodFilter 恰恰有一個獲取請求方式的操作:

  • String paramValue = request.getParameter(this.methodParam);
    

八、RESTful案例

1茫虽、準(zhǔn)備工作

和傳統(tǒng) CRUD 一樣刊苍,實現(xiàn)對員工信息的增刪改查。

  • 搭建環(huán)境

  • 準(zhǔn)備實體類

    package com.atguigu.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() {
       }
    }
    
  • 準(zhǔn)備dao模擬數(shù)據(jù)

    package com.atguigu.mvc.dao;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.atguigu.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", "aa@163.com", 1));
          employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
          employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
          employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
          employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 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);
       }
    }
    

2濒析、功能清單

功能 URL 地址 請求方式
訪問首頁√ / GET
查詢?nèi)繑?shù)據(jù)√ /employee GET
刪除√ /employee/2 DELETE
跳轉(zhuǎn)到添加數(shù)據(jù)頁面√ /toAdd GET
執(zhí)行保存√ /employee POST
跳轉(zhuǎn)到更新數(shù)據(jù)頁面√ /employee/2 GET
執(zhí)行更新√ /employee PUT

3正什、具體功能:訪問首頁

a>配置view-controller
<mvc:view-controller path="/" view-name="index"/>
b>創(chuàng)建頁面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" >
    <title>Title</title>
</head>
<body>
<h1>首頁</h1>
<a th:href="@{/employee}">訪問員工信息</a>
</body>
</html>

4、具體功能:查詢所有員工數(shù)據(jù)

a>控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
    Collection<Employee> employeeList = employeeDao.getAll();
    model.addAttribute("employeeList", employeeList);
    return "employee_list";
}
b>創(chuàng)建employee_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://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>

5号杏、具體功能:刪除

a>創(chuàng)建處理delete請求方式的表單
<!-- 作用:通過超鏈接控制表單的提交婴氮,將post請求轉(zhuǎn)換為delete請求 -->
<form id="delete_form" method="post">
    <!-- HiddenHttpMethodFilter要求:必須傳輸_method請求參數(shù),并且值為最終的請求方式 -->
    <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表示當(dāng)前事件
            deleteEmployee:function (event) {
                //通過id獲取表單標(biāo)簽
                var delete_form = document.getElementById("delete_form");
                //將觸發(fā)事件的超鏈接的href屬性為表單的action屬性賦值
                delete_form.action = event.target.href;
                //提交表單
                delete_form.submit();
                //阻止超鏈接的默認(rèn)跳轉(zhuǎn)行為
                event.preventDefault();
            }
        }
    });
</script>
c>控制器方法
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
    employeeDao.delete(id);
    return "redirect:/employee";
}

6盾致、具體功能:跳轉(zhuǎn)到添加數(shù)據(jù)頁面

a>配置view-controller
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
b>創(chuàng)建employee_add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://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>

7主经、具體功能:執(zhí)行保存

a>控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

8、具體功能:跳轉(zhuǎn)到更新數(shù)據(jù)頁面

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>創(chuàng)建employee_update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://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}"可用于單選框或復(fù)選框的回顯
        若單選框的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>

9罩驻、具體功能:執(zhí)行更新

a>控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
    employeeDao.save(employee);
    return "redirect:/employee";
}

八、HttpMessageConverter

HttpMessageConverter护赊,報文信息轉(zhuǎn)換器惠遏,將請求報文轉(zhuǎn)換為Java對象,或?qū)ava對象轉(zhuǎn)換為響應(yīng)報文

HttpMessageConverter提供了兩個注解和兩個類型:@RequestBody骏啰,@ResponseBody节吮,RequestEntity,

ResponseEntity

1判耕、@RequestBody

@RequestBody可以獲取請求體透绩,需要在控制器方法設(shè)置一個形參,使用@RequestBody進(jìn)行標(biāo)識祈秕,當(dāng)前請求的請求體就會為當(dāng)前注解所標(biāo)識的形參賦值

<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";
}

輸出結(jié)果:

requestBody:username=admin&password=123456

2渺贤、RequestEntity

RequestEntity封裝請求報文的一種類型,需要在控制器方法的形參中設(shè)置該類型的形參请毛,當(dāng)前請求的請求報文就會賦值給該形參志鞍,可以通過getHeaders()獲取請求頭信息,通過getBody()獲取請求體信息

@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
    System.out.println("requestHeader:"+requestEntity.getHeaders());
    System.out.println("requestBody:"+requestEntity.getBody());
    return "success";
}

輸出結(jié)果:
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用于標(biāo)識一個控制器方法固棚,可以將該方法的返回值直接作為響應(yīng)報文的響應(yīng)體響應(yīng)到瀏覽器

@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
    return "success";
}

結(jié)果:瀏覽器頁面顯示success

4统翩、SpringMVC處理json

@ResponseBody處理json的步驟:

a>導(dǎo)入jackson的依賴

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

b>在SpringMVC的核心配置文件中開啟mvc的注解驅(qū)動,此時在HandlerAdaptor中會自動裝配一個消息轉(zhuǎn)換器:MappingJackson2HttpMessageConverter此洲,可以將響應(yīng)到瀏覽器的Java對象轉(zhuǎn)換為Json格式的字符串

<mvc:annotation-driven />

c>在處理器方法上使用@ResponseBody注解進(jìn)行標(biāo)識

d>將Java對象直接作為控制器方法的返回值返回厂汗,就會自動轉(zhuǎn)換為Json格式的字符串

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

瀏覽器的頁面中展示的結(jié)果:

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

5、SpringMVC處理ajax

a>請求超鏈接:

<div id="app">
    <a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br>
</div>

b>通過vue和axios處理點擊事件:

<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">
    var vue = new Vue({
        el:"#app",
        methods:{
            testAjax:function (event) {
                axios({
                    method:"post",
                    url:event.target.href,
                    params:{
                        username:"admin",
                        password:"123456"
                    }
                }).then(function (response) {
                    alert(response.data);
                });
                event.preventDefault();
            }
        }
    });
</script>

c>控制器方法:

@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
    System.out.println("username:"+username+",password:"+password);
    return "hello,ajax";
}

6呜师、@RestController注解

@RestController注解是springMVC提供的一個復(fù)合注解娶桦,標(biāo)識在控制器的類上,就相當(dāng)于為類添加了@Controller注解汁汗,并且為其中的每個方法添加了@ResponseBody注解

7衷畦、ResponseEntity

ResponseEntity用于控制器方法的返回值類型,該控制器方法的返回值就是響應(yīng)到瀏覽器的響應(yīng)報文

九知牌、文件上傳和下載

1祈争、文件下載

使用ResponseEntity實現(xiàn)下載文件的功能

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

2、文件上傳

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

SpringMVC中將上傳的文件封裝到MultipartFile對象中菩混,通過此對象可以獲取文件相關(guān)信息

上傳步驟:

a>添加依賴:

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

b>在SpringMVC的配置文件中添加配置:

<!--必須通過文件解析器的解析才能將文件轉(zhuǎn)換為MultipartFile對象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

c>控制器方法:

@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;
    //獲取服務(wù)器中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;
    //實現(xiàn)上傳功能
    photo.transferTo(new File(finalPath));
    return "success";
}

十、攔截器

1扁藕、攔截器的配置

SpringMVC中的攔截器用于攔截控制器方法的執(zhí)行

SpringMVC中的攔截器需要實現(xiàn)HandlerInterceptor

SpringMVC的攔截器必須在SpringMVC的配置文件中進(jìn)行配置:

<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上兩種配置方式都是對DispatcherServlet所處理的所有的請求進(jìn)行攔截 -->
<mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/testRequestEntity"/>
    <ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!-- 
    以上配置方式可以通過ref或bean標(biāo)簽設(shè)置攔截器沮峡,通過mvc:mapping設(shè)置需要攔截的請求,通過mvc:exclude-mapping設(shè)置需要排除的請求纹磺,即不需要攔截的請求
-->

2帖烘、攔截器的三個抽象方法

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

preHandle:控制器方法執(zhí)行之前執(zhí)行preHandle()亮曹,其boolean類型的返回值表示是否攔截或放行橄杨,返回true為放行,即調(diào)用控制器方法照卦;返回false表示攔截式矫,即不調(diào)用控制器方法

postHandle:控制器方法執(zhí)行之后執(zhí)行postHandle()

afterComplation:處理完視圖和模型數(shù)據(jù),渲染視圖完畢之后執(zhí)行afterComplation()

3役耕、多個攔截器的執(zhí)行順序

a>若每個攔截器的preHandle()都返回true

此時多個攔截器的執(zhí)行順序和攔截器在SpringMVC的配置文件的配置順序有關(guān):

preHandle()會按照配置的順序執(zhí)行采转,而postHandle()和afterComplation()會按照配置的反序執(zhí)行

b>若某個攔截器的preHandle()返回了false

preHandle()返回false和它之前的攔截器的preHandle()都會執(zhí)行,postHandle()都不執(zhí)行瞬痘,返回false的攔截器之前的攔截器的afterComplation()會執(zhí)行

十一故慈、異常處理器

1、基于配置的異常處理

SpringMVC提供了一個處理控制器方法執(zhí)行過程中所出現(xiàn)的異常的接口:HandlerExceptionResolver

HandlerExceptionResolver接口的實現(xiàn)類有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver

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

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--
                properties的鍵表示處理器方法執(zhí)行過程中出現(xiàn)的異常
                properties的值表示若出現(xiàn)指定異常時察绷,設(shè)置一個新的視圖名稱,跳轉(zhuǎn)到指定頁面
            -->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--
        exceptionAttribute屬性設(shè)置一個屬性名津辩,將出現(xiàn)的異常信息在請求域中進(jìn)行共享
    -->
    <property name="exceptionAttribute" value="ex"></property>
</bean>

2拆撼、基于注解的異常處理

//@ControllerAdvice將當(dāng)前類標(biāo)識為異常處理的組件
@ControllerAdvice
public class ExceptionController {

    //@ExceptionHandler用于設(shè)置所標(biāo)識方法處理的異常
    @ExceptionHandler(ArithmeticException.class)
    //ex表示當(dāng)前請求處理中出現(xiàn)的異常對象
    public String handleArithmeticException(Exception ex, Model model){
        model.addAttribute("ex", ex);
        return "error";
    }

}

十二容劳、注解配置SpringMVC

使用配置類和注解代替web.xml和SpringMVC配置文件的功能

1、創(chuàng)建初始化類闸度,代替web.xml

在Servlet3.0環(huán)境中竭贩,容器會在類路徑中查找實現(xiàn)javax.servlet.ServletContainerInitializer接口的類,如果找到的話就用它來配置Servlet容器莺禁。
Spring提供了這個接口的實現(xiàn)留量,名為SpringServletContainerInitializer,這個類反過來又會查找實現(xiàn)WebApplicationInitializer的類并將配置的任務(wù)交給它們來完成哟冬。Spring3.2引入了一個便利的WebApplicationInitializer基礎(chǔ)實現(xiàn)肪获,名為AbstractAnnotationConfigDispatcherServletInitializer,當(dāng)我們的類擴(kuò)展了AbstractAnnotationConfigDispatcherServletInitializer并將其部署到Servlet3.0容器的時候柒傻,容器會自動發(fā)現(xiàn)它孝赫,并用它來配置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的映射規(guī)則红符,即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};
    }
}

2青柄、創(chuàng)建SpringConfig配置類,代替spring的配置文件

@Configuration
public class SpringConfig {
    //ssm整合之后预侯,spring的配置信息寫在此類中
}

3致开、創(chuàng)建WebConfig配置類,代替SpringMVC的配置文件

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

    //使用默認(rèn)的servlet處理靜態(tài)資源
    @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(firstInterceptor).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");
        //設(shè)置異常映射
        exceptionResolver.setExceptionMappings(prop);
        //設(shè)置共享異常信息的鍵
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }*/

    //配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一個ServletContext作為構(gòu)造參數(shù)萎馅,可通過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;
    }


}

4双戳、測試功能

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

十三、SpringMVC執(zhí)行流程

1糜芳、SpringMVC常用組件

  • DispatcherServlet:前端控制器飒货,不需要工程師開發(fā),由框架提供

作用:統(tǒng)一處理請求和響應(yīng)峭竣,整個流程控制的中心塘辅,由它調(diào)用其它組件處理用戶的請求

  • HandlerMapping:處理器映射器,不需要工程師開發(fā)皆撩,由框架提供

作用:根據(jù)請求的url扣墩、method等信息查找Handler,即控制器方法

  • Handler:處理器扛吞,需要工程師開發(fā)

作用:在DispatcherServlet的控制下Handler對具體的用戶請求進(jìn)行處理

  • HandlerAdapter:處理器適配器呻惕,不需要工程師開發(fā),由框架提供

作用:通過HandlerAdapter對處理器(控制器方法)進(jìn)行執(zhí)行

  • ViewResolver:視圖解析器滥比,不需要工程師開發(fā)亚脆,由框架提供

作用:進(jìn)行視圖解析,得到相應(yīng)的視圖守呜,例如:ThymeleafView型酥、InternalResourceView山憨、RedirectView

  • View:視圖

作用:將模型數(shù)據(jù)通過頁面展示給用戶

2、DispatcherServlet初始化過程

DispatcherServlet 本質(zhì)上是一個 Servlet弥喉,所以天然的遵循 Servlet 的生命周期郁竟。所以宏觀上是 Servlet 生命周期來進(jìn)行調(diào)度。

a>初始化WebApplicationContext

所在類:org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 創(chuàng)建WebApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // 刷新WebApplicationContext
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // 將IOC容器在應(yīng)用域共享
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}
b>創(chuàng)建WebApplicationContext

所在類:org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // 通過反射創(chuàng)建 IOC 容器對象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 設(shè)置父容器
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}
c>DispatcherServlet初始化策略

FrameworkServlet創(chuàng)建WebApplicationContext后由境,刷新容器棚亩,調(diào)用onRefresh(wac),此方法在DispatcherServlet中進(jìn)行了重寫虏杰,調(diào)用了initStrategies(context)方法讥蟆,初始化策略,即初始化DispatcherServlet的各個組件

所在類:org.springframework.web.servlet.DispatcherServlet

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

3纺阔、DispatcherServlet調(diào)用組件處理請求

a>processRequest()

FrameworkServlet重寫HttpServlet中的service()和doXxx()瘸彤,這些方法中調(diào)用了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 {
        // 執(zhí)行服務(wù),doService()是一個抽象方法笛钝,在DispatcherServlet中進(jìn)行了重寫
        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 {
        // 處理請求和響應(yīng)
        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:調(diào)用鏈
                包含handler质况、interceptorList、interceptorIndex
                handler:瀏覽器發(fā)送的請求所匹配的控制器方法
                interceptorList:處理控制器方法的所有攔截器集合
                interceptorIndex:攔截器索引玻靡,控制攔截器afterCompletion()的執(zhí)行
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 通過控制器方法創(chuàng)建相應(yīng)的處理器適配器结榄,調(diào)用所對應(yīng)的控制器方法
            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;
                }
            }
            
            // 調(diào)用攔截器的preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 由處理器適配器調(diào)用具體的控制器方法,最終獲得ModelAndView對象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 調(diào)用攔截器的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);
        }
        // 后續(xù)處理:處理模型數(shù)據(jù)和渲染視圖
        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()) {
        // 處理模型數(shù)據(jù)和渲染視圖
        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..
        // 調(diào)用攔截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

4囤捻、SpringMVC的執(zhí)行流程

  1. 用戶向服務(wù)器發(fā)送請求臼朗,請求被SpringMVC 前端控制器 DispatcherServlet捕獲。

  2. DispatcherServlet對請求URL進(jìn)行解析蝎土,得到請求資源標(biāo)識符(URI)视哑,判斷請求URI對應(yīng)的映射:

a) 不存在

i. 再判斷是否配置了mvc:default-servlet-handler

ii. 如果沒配置,則控制臺報映射查找不到瘟则,客戶端展示404錯誤

[
fEnrsP.png

iii. 如果有配置黎炉,則訪問目標(biāo)資源(一般為靜態(tài)資源,如:JS,CSS,HTML)醋拧,找不到客戶端也會展示404錯誤

b) 存在則執(zhí)行下面的流程

  1. 根據(jù)該URI,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對象(包括Handler對象以及Handler對象對應(yīng)的攔截器)淀弹,最后以HandlerExecutionChain執(zhí)行鏈對象的形式返回丹壕。

  2. DispatcherServlet 根據(jù)獲得的Handler,選擇一個合適的HandlerAdapter薇溃。

  3. 如果成功獲得HandlerAdapter菌赖,此時將開始執(zhí)行攔截器的preHandler(…)方法【正向】

  4. 提取Request中的模型數(shù)據(jù),填充Handler入?yún)逍颍_始執(zhí)行Handler(Controller)方法琉用,處理請求堕绩。在填充Handler的入?yún)⑦^程中,根據(jù)你的配置邑时,Spring將幫你做一些額外的工作:

a) HttpMessageConveter: 將請求消息(如Json奴紧、xml等數(shù)據(jù))轉(zhuǎn)換成一個對象,將對象轉(zhuǎn)換為指定的響應(yīng)信息

b) 數(shù)據(jù)轉(zhuǎn)換:對請求消息進(jìn)行數(shù)據(jù)轉(zhuǎn)換晶丘。如String轉(zhuǎn)換成Integer黍氮、Double等

c) 數(shù)據(jù)格式化:對請求消息進(jìn)行數(shù)據(jù)格式化。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等

d) 數(shù)據(jù)驗證: 驗證數(shù)據(jù)的有效性(長度浅浮、格式等)沫浆,驗證結(jié)果存儲到BindingResult或Error中

  1. Handler執(zhí)行完成后,向DispatcherServlet 返回一個ModelAndView對象滚秩。

  2. 此時將開始執(zhí)行攔截器的postHandle(...)方法【逆向】专执。

  3. 根據(jù)返回的ModelAndView(此時會判斷是否存在異常:如果存在異常,則執(zhí)行HandlerExceptionResolver進(jìn)行異常處理)選擇一個適合的ViewResolver進(jìn)行視圖解析郁油,根據(jù)Model和View他炊,來渲染視圖。

  4. 渲染視圖完畢執(zhí)行攔截器的afterCompletion(…)方法【逆向】已艰。

  5. 將渲染結(jié)果返回給客戶端痊末。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哩掺,隨后出現(xiàn)的幾起案子凿叠,更是在濱河造成了極大的恐慌,老刑警劉巖嚼吞,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盒件,死亡現(xiàn)場離奇詭異,居然都是意外死亡舱禽,警方通過查閱死者的電腦和手機(jī)炒刁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來誊稚,“玉大人翔始,你說我怎么就攤上這事±锊” “怎么了城瞎?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疾瓮。 經(jīng)常有香客問我脖镀,道長,這世上最難降的妖魔是什么狼电? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任蜒灰,我火速辦了婚禮弦蹂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘强窖。我一直安慰自己凸椿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布毕骡。 她就那樣靜靜地躺著削饵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪未巫。 梳的紋絲不亂的頭發(fā)上窿撬,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音叙凡,去河邊找鬼劈伴。 笑死,一個胖子當(dāng)著我的面吹牛握爷,可吹牛的內(nèi)容都是我干的跛璧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼新啼,長吁一口氣:“原來是場噩夢啊……” “哼追城!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起燥撞,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤座柱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后物舒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體色洞,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年冠胯,在試婚紗的時候發(fā)現(xiàn)自己被綠了火诸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡荠察,死狀恐怖置蜀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情割粮,我是刑警寧澤盾碗,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站舀瓢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耗美。R本人自食惡果不足惜京髓,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一航缀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堰怨,春花似錦芥玉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至揽涮,卻和暖如春抠藕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒋困。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工盾似, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雪标。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓零院,卻偏偏與公主長得像,于是被迫代替她去往敵國和親村刨。 傳聞我的和親對象是個殘疾皇子告抄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容