SpringMVC學習筆記

看海、聽風發表於2022-12-23

1、認識SpringMVC

1、什麼是MVC


MVC是一種軟體架構的思想,將軟體按照模型、檢視、控制器來劃分

M:Model,模型層,指工程中的JavaBean,作用是處理資料

JavaBean分為兩類:

  • 一類稱為實體類Bean:專門儲存業務資料的,如 Student、User 等
  • 一類稱為業務處理 Bean:指 Service 或 Dao 物件,專門用於處理業務邏輯和資料訪問。

V:View,檢視層,指工程中的html或jsp等頁面,作用是與使用者進行互動,展示資料

C:Controller,控制層,指工程中的servlet,作用是接收請求和響應瀏覽器

MVC的工作流程:
使用者透過檢視層傳送請求到伺服器,在伺服器中請求被Controller接收,Controller呼叫相應的Model層處理請求,處理完畢將結果返回到Controller,Controller再根據請求處理的結果找到相應的View檢視,渲染資料後最終響應給瀏覽器

2、什麼是SpringMVC

SpringMVC是Spring的一個後續產品,是Spring的一個子專案

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

注:三層架構分為表述層(或表示層)、業務邏輯層、資料訪問層,表述層表示前臺頁面和後臺servlet

2、HelloSpringMVC


1、快速入門

匯入依賴

<dependency>  
	<groupId>org.springframework</groupId>  
	<artifactId>spring-webmvc</artifactId>  
	<version>5.3.21</version>  
</dependency>    
<dependency>        
	<groupId>javax.servlet</groupId>  
	<artifactId>servlet-api</artifactId>  
	<version>2.3</version>  
</dependency>

新增web框架


在web.xml中配置servlet

<?xml version="1.0" encoding="UTF-8"?>  
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
         version="4.0">  
    <servlet>        
	    <servlet-name>dispatcherServlet</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>            
	        <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:SpringMVC.xml</param-value>  
        </init-param>        
        <load-on-startup>1</load-on-startup>  
    </servlet>    
    <servlet-mapping>        
	    <servlet-name>dispatcherServlet</servlet-name>  
        <!--配置 / 則不能匹配jsp靜態資源的請求-->  
        <!--配置 /* 匹配所有請求-->  
        <!--此配置覆蓋了tomcat中的預設servlet,但是jsp檔案有別的servlet匹配-->  
        <url-pattern>/</url-pattern>  
    </servlet-mapping>
</web-app>

關於<url-pattern>為什麼要設定成/

tomcat提供的兩個Servlet

1、DefaultServlet
DefaultServlet為預設的Servlet,當客戶端請求不能匹配其他所有Servlet時,將由DefaultServlet處理。它配置的url-pattern為/。

DefaultServlet主要用於處理靜態資源,如HTML、圖片、CSS、JS檔案等,而且為了提升伺服器效能,Tomcat對訪問檔案進行快取。按照預設配置,客戶端請求路徑與資源的物理路徑是一致的。

如果我們希望Web應用覆蓋Tomcat的DefaultServlet配置,只需將“ / ”新增到自定義Servlet的url-pattern中即可(此時,自定義Servlet將成為Web應用的預設的Servlet)。

2、JspServlet
預設情況下,JspServlet的url-pattern為.jsp和.jspx,因此他負責處理所有JSP檔案的請求。

JspServlet主要做了這些事情:

根據JSP檔案生成對應Servlet的Java程式碼(JSP檔案生成類的父類為org.apache.jasper.runtime.HttpJspBase——實現了Servlet介面)。
將Java程式碼編譯為Java Class。Tomcat支援Ant和JDT(Eclipse提供的編譯器)兩種編譯JSP類,預設採用JDT。
構造Servlet類例項並且執行請求

DispatcherServlet的配置/和/*的區別

如果把DispatcherServlet的url-pattern配置成/*,那麼它會覆蓋掉jsp servlet,所有的jsp請求最交給DispatchServlet處理,如果Controller中沒有配置相關處理方法那麼會無法處理。事實上沒有必要越俎代庖的處理.jsp請求,完全可以交給Tomcat容器處理jsp請求,因此DispatchServlet要配置成/。

配置SpringMVC配置檔案

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">  
  
    <!--元件掃描-->  
    <context:component-scan base-package="com.zh.controller"/>  
  
    <!-- <mvc:annotation-driven /> 會自動註冊DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter 兩個bean,  
    是spring MVC為@Controllers分發請求所必須的。它提供了資料繫結支援,讀取json的支援 -->  
    <mvc:annotation-driven/>  
  
    <!--訪問靜態資源時使用預設的處理器處理,若不配置,則所有請求都當做正常請求處理,會新增檢視解析器的前字尾,則會找不到資源-->  
    <mvc:default-servlet-handler/>  
    <!-- 配置jsp檢視解析器 -->  
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="jspViewResolver">  
        <property name="prefix" value="/WEB-INF/jsp/"/>  
        <property name="suffix" value=".jsp"/>  
    </bean>  
</beans>

控制器

@Controller  
public class HelloController {  
  
    @RequestMapping("/hello")  
    public String Hello(){  
        return "hello";  
    }  
}

環境搭建成功

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

3、@RequestMapping註解


1、@RequestMapping註解的功能

從註解名稱上我們可以看到,@RequestMapping註解的作用就是將請求和處理請求的控制器方法關聯起來,建立對映關係。

SpringMVC 接收到指定的請求,就會來找到在對映關係中對應的控制器方法來處理這個請求。

2、@RequestMapping註解的位置

@RequestMapping標識一個類:設定對映請求的請求路徑的初始資訊

@RequestMapping標識一個方法:設定對映請求請求路徑的具體資訊

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

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

}

3、@RequestMapping註解的value屬性

@RequestMapping註解的value屬性透過請求的請求地址匹配請求對映

@RequestMapping註解的value屬性是一個字串型別的陣列,表示該請求對映能夠匹配多個請求地址所對應的請求

@RequestMapping註解的value屬性必須設定,至少透過請求地址匹配請求對映

@Controller  
public class Test {  
  
    @RequestMapping(value = {"/test01","/test02"})  
    public String test01(){  
        return "test";  
    }  
}

4、@RequestMapping註解的method屬性

@RequestMapping註解的method屬性透過請求的請求方式(get或post)匹配請求對映

@RequestMapping註解的method屬性是一個RequestMethod型別的陣列,表示該請求對映能夠匹配多種請求方式的請求

若當前請求的請求地址滿足請求對映的value屬性,但是請求方式不滿足method屬性,則瀏覽器報錯405:Request method 'POST' not supported

@RequestMapping(
        value = {"/testRequestMapping", "/test"},
        method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    return "success";
}

注:

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

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

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

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

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

2、常用的請求方式有get,post,put,delete

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

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

5、@RequestMapping註解的params屬性(瞭解)

@RequestMapping註解的params屬性透過請求的請求引數匹配請求對映

@RequestMapping註解的params屬性是一個字串型別的陣列,可以透過四種表示式設定請求引數和請求對映的匹配關係

"param":要求請求對映所匹配的請求必須攜帶param請求引數

"!param":要求請求對映所匹配的請求必須不能攜帶param請求引數

"param=value":要求請求對映所匹配的請求必須攜帶param請求引數且param=value

"param!=value":要求請求對映所匹配的請求必須攜帶param請求引數但是param!=value

注:

若當前請求滿足@RequestMapping註解的value和method屬性,但是不滿足params屬性,此時頁面回報錯400:Parameter conditions "username, password!=123456" not met for actual request parameters: username={admin}, password={123456}

6、@RequestMapping註解的headers屬性(瞭解)

@RequestMapping註解的headers屬性透過請求的請求頭資訊匹配請求對映

@RequestMapping註解的headers屬性是一個字串型別的陣列,可以透過四種表示式設定請求頭資訊和請求對映的匹配關係

"header":要求請求對映所匹配的請求必須攜帶header請求頭資訊

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

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

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

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

7、SpringMVC支援ant風格的路徑

?:表示任意的單個字元

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

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

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

8、SpringMVC支援路徑中的佔位符(重點)

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

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

4、SpringMVC獲取請求引數


1、透過ServletAPI獲取

將HttpServletRequest作為控制器方法的形參,此時HttpServletRequest型別的參數列示封裝了當前請求的請求報文的物件

//1、透過ServletAPI獲取  
@RequestMapping("/test01")  
public String test01(HttpServletRequest request){  
    String name = request.getParameter("name");  
    String id = request.getParameter("id");  
  
    System.out.println("=====test01=====");  
    System.out.println(name);  
    System.out.println(id);  
  
    return "test";  
}

2、透過控制器方法的形參獲取請求引數

在控制器方法的形參位置,設定和請求引數同名的形參,當瀏覽器傳送請求,匹配到請求對映時,在DispatcherServlet中就會將請求引數賦值給相應的形參

//2、透過請求引數名字  
@RequestMapping("/test02")  
public String test02(String name,String id){  
  
    System.out.println("=====test02=====");  
    System.out.println(name);  
    System.out.println(id);  
  
    return "test";  
}

注:

若請求所傳輸的請求引數中有多個同名的請求引數,此時可以在控制器方法的形參中設定字串陣列或者字串型別的形參接收此請求引數

若使用字串陣列型別的形參,此引數的陣列中包含了每一個資料

若使用字串型別的形參,此引數的值為每個資料中間使用逗號拼接的結果

作用:獲取前端多選框資料

3、@RequestParam

@RequestParam是將請求引數和控制器方法的形參建立對映關係

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

value:指定為形參賦值的請求引數的引數名

required:設定是否必須傳輸此請求引數,預設值為true

若設定為true時,則當前請求必須傳輸value所指定的請求引數,若沒有傳輸該請求引數,且沒有設定defaultValue屬性,則頁面報錯400:Required String parameter 'xxx' is not present;若設定為false,則當前請求不是必須傳輸value所指定的請求引數,若沒有傳輸,則註解所標識的形參的值為null

defaultValue:不管required屬性值為true或false,當value所指定的請求引數沒有傳輸或傳輸的值為""時,則使用預設值為形參賦值

//3、當前端引數名和方法形參名字不一樣時透過@RequestParam獲取  
@RequestMapping("/test03")  
public String test03(@RequestParam("name") String username, String id){  
  
    System.out.println("=====test03=====");  
    System.out.println(username);  
    System.out.println(id);  
  
    return "test";  
}

4、@RequestHeader

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

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

//4、獲取請求頭  
@RequestMapping("/test04")  
public String test04(@RequestHeader("host") String host){  
  
    System.out.println("=====test04=====");  
    System.out.println(host);  
  
    return "test";  
}

5、@CookieValue

@CookieValue是將cookie資料和控制器方法的形參建立對映關係

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

//5、獲取cookie引數  
@RequestMapping("/test05")  
public String test05(@CookieValue("JSESSIONID") String session){  
  
    System.out.println("=====test05=====");  
    System.out.println(session);  
  
    return "test";  
}

6、透過POJO獲取請求引數

可以在控制器方法的形參位置設定一個實體類型別的形參,此時若瀏覽器傳輸的請求引數的引數名和實體類中的屬性名一致,那麼請求引數就會為此屬性賦值

<form action="/SpringMVC_03_Parameter/pojo" method="post">  
  使用者名稱:<input type="text" name="username"><br>  
  密碼:<input type="password" name="password"><br>  
  性別:<input type="radio" name="sex" value="男">男  
  <input type="radio" name="sex" value="女">女<br>  
  年齡:<input type="text" name="age"><br>  
  <input type="submit">  
</form>
public class User {  
    private String username;  
    private String password;  
    private String sex;  
    private String age;  
  
    public User() {  
    }
    ......
//6、獲取屬性自動封裝到類中  
@RequestMapping("/pojo")  
public String test06(User user){  
  
    System.out.println("=====pojo=====");  
    System.out.println(user);  
  
    return "test";  
}

User{username='張三', password='123321', sex='男', age='11'}

7、解決獲取請求引數的亂碼問題

配置過濾器

<!--配置springMVC的編碼過濾器-->  
<filter>  
    <filter-name>CharacterEncodingFilter</filter-name>  
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
    <init-param>        
	    <param-name>encoding</param-name>  
        <param-value>UTF-8</param-value>  
    </init-param>
</filter>  
  
<filter-mapping>  
    <filter-name>CharacterEncodingFilter</filter-name>  
    <!--這裡設定成/*--> 
    <url-pattern>/*</url-pattern>  
</filter-mapping>

注:

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

5、域物件共享資料

1、使用ServletAPI向request域物件共享資料

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

2、使用ModelAndView向request域物件共享資料

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

3、使用Model向request域物件共享資料

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

4、使用map向request域物件共享資料

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

5、使用ModelMap向request域物件共享資料

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

6、Model、ModelMap、Map的關係

Model、ModelMap、Map型別的引數其實本質上都是 BindingAwareModelMap 型別的

public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

7、向session域共享資料

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

8、向application域共享資料

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

9、回顧四大域物件

【1】ServletContext 域--- application
ServletContext代表整個web應用的物件。

生命週期:web應用被tomcat伺服器載入時,ServletContext物件產生,生命週期開始。

web應用被移除容器或者tomcat伺服器關閉的時候,ServletContext物件銷燬,生命週期結束。

作用範圍:整個web應用。

主要功能:在整個web應用範圍內共享資料。

【2】session 域---session
Session代表整個會話的物件

生命週期:當呼叫request.getSession()時,Session物件被建立。生命週期開始

呼叫session.invalidate()方法銷燬Session物件

在設定的時間內,Session物件沒有被使用,則Session物件被銷燬。預設為30分鐘

當伺服器意外關閉的時候,Session物件被銷燬。當伺服器正常關閉的時候,Session物件中仍有資料,會序列化到磁碟上形成一個檔案,

這個過程稱之為鈍化。在伺服器再次啟動的時候,這個檔案會被重新讀取到伺服器中使用,這個過程稱之為活化。

作用範圍:整個會話範圍

主要功能:在會話範圍內共享資料

【3】request 域---request
Request代表請求的物件

生命週期:請求鏈開始,request物件被建立,請求鏈結束,request物件銷燬。

作用範圍:整個請求鏈

主要功能:在請求鏈內共享資料

【4】pageContext域---pageContext
PageContext代表當前頁面的物件

生命週期:在訪問jsp頁面時,pageContext物件產生,生命週期開始。在結束訪問jsp頁面時,pageContext物件銷燬,生命週期結束。

作用範圍:整個jsp頁面

主要功能:在整個jsp頁面內共享資料

6、HttpMessageConverter

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

HttpMessageConverter提供了兩個註解和兩個型別:@RequestBody,@ResponseBody,RequestEntity,

ResponseEntity

1、@RequestBody

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

<form action="/SpringMVC_03_Parameter/pojo" method="post">  
  使用者名稱:<input type="text" name="username"><br>  
  密碼:<input type="password" name="password"><br>  
  性別:<input type="radio" name="sex" value="男">男  
  <input type="radio" name="sex" value="女">女<br>  
  年齡:<input type="text" name="age"><br>  
  <input type="submit">  
</form>
public String test06(User user,@RequestBody String body){  
    System.out.println("=====pojo=====");  
  
    System.out.println(body);  
  
    System.out.println(user);

	return null;
}

輸出結果:

username=%C3%A5%C2%BC%C2%A0%C3%A4%C2%B8%C2%89&password=111&sex=%C3%A7%C2%94%C2%B7&age=11
User{username='??????', password='111', sex='??·', age='11'}

2、RequestEntity

RequestEntity封裝請求報文的一種型別,需要在控制器方法的形參中設定該型別的形參,當前請求的請求報文就會賦值給該形參,可以透過getHeaders()獲取請求頭資訊,透過getBody()獲取請求體資訊

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

輸出結果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"", sec-ch-ua-mobile:"?0", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"]
requestBody:username=admin&password=123

3、@ResponseBody

@ResponseBody用於標識一個控制器方法,可以將該方法的返回值直接作為響應報文的響應體響應到瀏覽器

@RequestMapping("/pojo")  
@ResponseBody  
public String test06(User user){  
    System.out.println("=====pojo=====");  
  
    System.out.println(user);  
  
    return user.toString();  
}

結果:瀏覽器頁面顯示User{username='张三', password='132', sex='ç”·', age='11'}

控制檯輸出
pojo
User{username='??????', password='132', sex='??·', age='11'}

4、解決亂碼問題

亂碼問題我認為可以分為兩種

1、獲取前端引數亂碼

解決方法:配置過濾器

<filter>  
    <filter-name>CharacterEncodingFilter</filter-name>  
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
    <init-param>        
	    <param-name>encoding</param-name>  
        <param-value>UTF-8</param-value>  
    </init-param></filter>  
  
<filter-mapping>  
    <filter-name>CharacterEncodingFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>

過濾所有請求:/*

配置好後,控制檯輸出:
pojo
User{username='張三', password='123321', sex='男', age='111'}

2、後端返回資料後前端顯示亂碼

開啟前端控制檯可以發現響應報文有如下資訊:
Content-Type:text/html;charset=ISO-8859-1

這就是導致亂碼的原因

為什麼返回的資料編碼是ISO-8859-1?

在spring處理ResponseBody時涉及到org.springframework.http.converter.StringHttpMessageConverter這個類,該類在預設實現中將defaultCharset設為ISO-8859-1。當@RequestMapping標記的方法未配置produces屬性時,將自動使用預設編碼;如果配置了produces屬性,AbstractHttpMessageConverter中的write方法將不會受supportedMediaTypes影響,而用produce設定的header賦值給contentType。

原始碼預設為ISO-8859-1

因此,解決方法是:指定給訊息轉換器的編碼為utf-8

解決方法:

1、配置@RequestMapping(value = "/pojo",produces = "text/html;charset=UTF-8")

2、配置 HttpMessageConverter

<mvc:annotation-driven>  
    <mvc:message-converters>  
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">  
            <property name="supportedMediaTypes">  
                <list>                    
	                <value>text/html;charset=UTF-8</value>  
                </list>            
			</property>        
		</bean>    
	</mvc:message-converters>  
</mvc:annotation-driven>

這裡值設定了返回一種媒體型別,可以設定多種,SpringMVC會自動尋找合適的訊息轉換器

關於過濾器為什麼無法設定ContentType

@RequestMapping(value = "/rulelist", method = RequestMethod.GET)

@ResponseBody

public String getRuleList(HttpServletRequest request,

        HttpServletResponse response) {

    response.addHeader("test", "test");

    return service.getRuleList();

}

透過驗證,我們可以看到test項已經被成功新增到response的頭部資訊

`Content-Length: 2 kilobytes`

`Content-Type:   text/plain;charset=ISO-8859-1`

`Server: Apache-Coyote/1.1`

`test: test`
@RequestMapping(value = "/rulelist", method = RequestMethod.GET)

@ResponseBody

public String getRuleList(HttpServletRequest request,

        HttpServletResponse response) {

    response.addHeader("Content-Type", "application/json;charset=UTF-8");

    return service.getRuleList();

}

沒有設定成功

`Content-Length: 2 kilobytes`

`Content-Type:   text/plain;charset=ISO-8859-1`

`Server: Apache-Coyote/1.1`

下圖清晰地向大家展示了Spring MVC處理HTTP請求的流程,(圖片來自網路)

具體流程如下:

1、DispatcherServlet接收到Request請求

2、HandlerMapping選擇一個合適的Handler處理Request請求

3-4、 選擇合適的HandlerAdapter,呼叫使用者編寫的Controller處理業務邏輯。(HandlerAdapter主要是幫助Spring MVC支援多種型別的Controller)

5、Controller將返回結果放置到Model中並且返回view名稱給Handler Adapter

6、DispatcherServlet選擇合適的ViewResolver來生成View物件

7-8、 View物件利用Model中的資料進行渲染並返回資料

從上面的流程圖我們可以看到,content-type header是單獨被處理的,media-type被單獨的邏輯進行處理,因此直接在ServletResponse中設定content-type header並不能正常生效。

5、SpringMVC處理json

@ResponseBody處理json的步驟:

a>匯入jackson的依賴

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

b>在SpringMVC的核心配置檔案中開啟mvc的註解驅動,此時在HandlerAdaptor中會自動裝配一個訊息轉換器:MappingJackson2HttpMessageConverter,可以將響應到瀏覽器的Java物件轉換為Json格式的字串

<mvc:annotation-driven />

c>在處理器方法上使用@ResponseBody註解進行標識

d>將Java物件直接作為控制器方法的返回值返回,就會自動轉換為Json格式的字串

@RequestMapping("/pojo")  
@ResponseBody  
public User test06(User user){  
    System.out.println("=====pojo=====");  
  
    System.out.println(user);  
  
    return user;  
}

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

{"username":"張三","password":"123321","sex":"男","age":"11"}

6、@RestController註解

@RestController註解是springMVC提供的一個複合註解,標識在控制器的類上,就相當於為類新增了@Controller註解,並且為其中的每個方法新增了@ResponseBody註解

7、ResponseEntity

ResponseEntity用於控制器方法的返回值型別,該控制器方法的返回值就是響應到瀏覽器的響應報文

7、SpringMVC的檢視

SpringMVC中的檢視是View介面,檢視的作用渲染資料,將模型Model中的資料展示給使用者

SpringMVC檢視的種類很多,預設有轉發檢視和重定向檢視

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

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

1、ThymeleafView

當控制器方法中所設定的檢視名稱沒有任何字首時,此時的檢視名稱會被SpringMVC配置檔案中所配置的檢視解析器解析,檢視名稱拼接檢視字首和檢視字尾所得到的最終路徑,會透過轉發的方式實現跳轉

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

2、轉發檢視

SpringMVC中預設的轉發檢視是InternalResourceView

SpringMVC中建立轉發檢視的情況:

當控制器方法中所設定的檢視名稱以"forward:"為字首時,建立InternalResourceView檢視,此時的檢視名稱不會被SpringMVC配置檔案中所配置的檢視解析器解析,而是會將字首"forward:"去掉,剩餘部分作為最終路徑透過轉發的方式實現跳轉

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

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

例項

@RequestMapping("/test07")  
public String test07(){  
    System.out.println("進入Test07");  
    return "forward:/test08";  
}  
@RequestMapping("/test08")  
public ModelAndView test08(User user){  
    ModelAndView modelAndView = new ModelAndView();  
  
    System.out.println("進入Test08");  
  
    modelAndView.addObject("user",user.toString());  
  
    modelAndView.setViewName("user");  
  
    return modelAndView;  
  
}

3、重定向檢視

SpringMVC中預設的重定向檢視是RedirectView

當控制器方法中所設定的檢視名稱以"redirect:"為字首時,建立RedirectView檢視,此時的檢視名稱不會被SpringMVC配置檔案中所配置的檢視解析器解析,而是會將字首"redirect:"去掉,剩餘部分作為最終路徑透過重定向的方式實現跳轉

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

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

注:
重定向檢視在解析時,會先將redirect:字首去掉,然後會判斷剩餘部分是否以/開頭,若是則會自動拼接上下文路徑

4、檢視控制器view-controller

當控制器方法中,僅僅用來實現頁面跳轉,即只需要設定檢視名稱時,可以將處理器方法使用view-controller標籤進行表示

<!--
	path:設定處理的請求地址
	view-name:設定請求地址所對應的檢視名稱
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>

注:

當SpringMVC中設定任何一個view-controller時,其他控制器中的請求對映將全部失效,此時需要在SpringMVC的核心配置檔案中設定開啟mvc註解驅動的標籤:

<mvc:annotation-driven />

8、RESTful

1、RESTful簡介

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

a>資源

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

b>資源的表述

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

c>狀態轉移

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

2、RESTful的實現

具體說,就是 HTTP 協議裡面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。

它們分別對應四種基本操作:GET 用來獲取資源,POST 用來新建資源,PUT 用來更新資源,DELETE 用來刪除資源。

REST 風格提倡 URL 地址使用統一的風格設計,從前到後各個單詞使用斜槓分開,不使用問號鍵值對方式攜帶請求引數,而是將要傳送給伺服器的資料作為 URL 地址的一部分,以保證整體風格的一致性。

操作 傳統方式 REST風格
查詢操作 getUserById?id=1 user/1-->get請求方式
儲存操作 saveUser user-->post請求方式
刪除操作 deleteUser?id=1 user/1-->delete請求方式
更新操作 updateUser user-->put請求方式

3、HiddenHttpMethodFilter

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

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

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

a>當前請求的請求方式必須為post

b>當前請求必須傳輸請求引數_method

滿足以上條件,HiddenHttpMethodFilter 過濾器就會將當前請求的請求方式轉換為請求引數_method的值,因此請求引數_method的值才是最終的請求方式

在web.xml中註冊HiddenHttpMethodFilter

<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注:

目前為止,SpringMVC中提供了兩個過濾器:CharacterEncodingFilter和HiddenHttpMethodFilter

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

原因:

  • 在 CharacterEncodingFilter 中透過 request.setCharacterEncoding(encoding) 方法設定字符集的

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

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

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

4、RESTful案例

準備工作
配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:mvc="http://www.springframework.org/schema/mvc"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/mvc       https://www.springframework.org/schema/mvc/spring-mvc.xsd       http://www.springframework.org/schema/context       https://www.springframework.org/schema/context/spring-context.xsd">  
  
  
    <context:component-scan base-package="com.zh"/>  
  
    <mvc:default-servlet-handler/>  
  
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="jspViewResolver">  
        <property name="prefix" value="/WEB-INF/jsp/"/>  
        <property name="suffix" value=".jsp"/>  
    </bean>  
    <mvc:annotation-driven>  
        <mvc:message-converters>  
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">  
                <property name="supportedMediaTypes">  
                    <list>                        
	                    <value>application/json;charset=UTF-8</value>  
                        <value>text/html;charset=UTF-8</value>  
                    </list>                
				</property>            
			</bean>        
		</mvc:message-converters>  
    </mvc:annotation-driven>  
  
    <mvc:view-controller path="/" view-name="index"/>  
  
    <mvc:view-controller path="/UserList" view-name="userlist"/>  
    <mvc:view-controller path="/addUser" view-name="addUser"/>  
  
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>  
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
         version="4.0">  
  
    <filter>        
	    <filter-name>characterEncodingFilter</filter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>            
	        <param-name>encoding</param-name>  
            <param-value>utf-8</param-value>  
        </init-param>        
        <init-param>            
	        <param-name>forceResponseEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>    
	</filter>    
	<filter-mapping>        
		<filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
    <filter>        
	    <filter-name>HiddenHttpMethodFilter</filter-name>  
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>  
    </filter>    
    <filter-mapping>        
	    <filter-name>HiddenHttpMethodFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
  
    <servlet>        
	    <servlet-name>dispatcherServlet</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>            
	        <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:SpringMVC.xml</param-value>  
        </init-param>    
	</servlet>    
	<servlet-mapping>        
		<servlet-name>dispatcherServlet</servlet-name>  
        <url-pattern>/</url-pattern>  
    </servlet-mapping>  
</web-app>

User類

package com.zh.pojo;  
  
import java.util.Objects;  
  
public class User {  
    private int id;  
    private String name;  
    private int age;  
    //省略set和get....
}

DAO類

package com.zh.userDAO;  
  
import com.zh.pojo.User;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class UserDAO {  
  
    private List<User> userList = new ArrayList<>();  
  
    private int idNum = 4;  
  
    public UserDAO() {  
        userList.add(new User(1,"張三",12));  
        userList.add(new User(2,"李四",14));  
        userList.add(new User(3,"王五",16));  
        userList.add(new User(4,"趙六",17));  
    }  
  
  
    //查詢所有使用者  
    public List<User> getUserList(){  
        return userList;  
    }  
  
    //根據使用者id查詢使用者  
    public User getUserById(int id){  
        for (User user : userList) {  
            if (user.getId() == id){  
                return user;  
            }  
        }  
        return null;  
    }  
  
    //新增使用者  
    public boolean addUser(User user){  
        idNum++;  
        user.setId(idNum);  
        return userList.add(user);  
    }  
  
  
    //根據id刪除使用者  
    public boolean delUser(int id){  
        for (User user : userList) {  
            if (user.getId() == id){  
                return userList.remove(user);  
            }  
        }  
        return false;  
    }  
  
    //根據id修改使用者  
    public boolean updateUser(int id,User newUser){  
        for (User user : userList) {  
            if (user.getId() == id){  
                user.setAge(newUser.getAge());  
                user.setName(newUser.getName());  
                return true;            }  
        }  
        return false;  
    }  
}

Controller類

package com.zh.controller;  
  
import com.fasterxml.jackson.core.JsonProcessingException;  
import com.fasterxml.jackson.databind.ObjectMapper;  
import com.zh.pojo.User;  
import com.zh.userDAO.UserDAO;  
import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestMethod;  
import org.springframework.web.bind.annotation.ResponseBody;  
  
/**  
 * RESRFul風格例項  
 */  
  
  
@Controller  
public class Controller01 {  
  
    private UserDAO userDAO = new UserDAO();  
  
    /**  
     * 使用RESTFul模擬操作使用者資訊  
     * /user    get    查詢所有使用者資訊  
     * /user/1  get    查詢指定使用者資訊  
     * /user    post    增加使用者資訊  
     * /user/1  delete 刪除指定使用者資訊  
     * /user/1  put    修改指定使用者資訊  
     */  
  
    //查詢所有使用者,返回JSON陣列  
    @RequestMapping(value = "/user",method = RequestMethod.GET)  
    @ResponseBody  
    public String getUser() throws JsonProcessingException {  
  
        ObjectMapper objectMapper = new ObjectMapper();  
        String userListString = objectMapper.writeValueAsString(userDAO.getUserList());  
  
        return userListString;  
    }  
  
    //查詢一個使用者,並將使用者資料繫結到user.jsp頁面上並進行跳轉  
    @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)  
    public String getUserById(@PathVariable("id") int id, Model model){  
  
        User user = userDAO.getUserById(id);  
        model.addAttribute("user",user);  
  
        return "user";  
    }  
  
    //增加一個使用者,然後重定向到列表頁面  
    @RequestMapping(value = "/user",method = RequestMethod.POST)  
    public String addUser(String username,int age){  
  
        userDAO.addUser(new User(username,age));  
        return "redirect:/UserList";  
    }  
  
    //刪除一個使用者  
    @RequestMapping(value = "/user",method = RequestMethod.DELETE)  
    public String delUser(int id){  
        userDAO.delUser(id);  
        return "redirect:/UserList";  
    }  
  
    //修改使用者資訊  
    @RequestMapping(value = "/user",method = RequestMethod.PUT)  
    @ResponseBody  
    public void updUser(int id,String username,int age){  
        userDAO.updateUser(id,new User(username,age));  
    }  
}

JSP頁面
UserList

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>UserList</title>  
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>  
    <style type="text/css">  
        table{  
            margin-top: 200px;  
        }  
        td{  
            text-align: center;  
        }  
        a{  
            text-decoration: none;  
        }  
    </style>  
</head>  
<body>  
    <table border="1" cellspacing="0" align="center" width="25%" id="table">  
  
    </table>    <p align="center">  
        <input type="button" id="btn1" value="重新整理">&nbsp;&nbsp;&nbsp;&nbsp;  
        <input type="button" id="btn2" value="增加">  
    </p>  
</body>  
<script>  
    function  getPath(){  
        let pathName = document.location.pathname;  
        let index = pathName.substr(1).indexOf("/");  
        let result = pathName.substr(0,index+1);  
        return result;  
    }  
    function aDelClick(){  
        var as = document.getElementsByClassName('del');  
        for (let i = 0; i < as.length; i++) {  
            as[i].onclick = function (){  
                let tr = this.parentNode.parentNode;  
                let id = tr.children[0].innerHTML;  
                let name = tr.children[1].innerHTML;  
                var flag = confirm("確認刪除"+ name + "嗎?");  
                if(flag){  
                    //刪除tr對應使用者的資訊  
                    $.ajax({  
                        type: 'post',  
                        dateType: 'json',  
                        url:getPath()+"/user",  
                        data: {  
                            _method:'delete',  
                            id:id  
                        },  
                        success:function (){  
  
                        }  
                    })  
                    tr.parentNode.removeChild(tr);  
                }  
            }  
        }  
    }  
    function aUpdClick(){  
        var as = document.getElementsByClassName('upd');  
        for (let i = 0; i < as.length; i++) {  
            as[i].onclick = function (){  
                let tr = this.parentNode.parentNode;  
                let id = tr.children[0].innerHTML;  
                window.location = getPath()+'/user'+'/'+id;  
            }  
        }  
    }  
    function getUserList(){  
        var table = $("table");  
        $.ajax({  
            type:"get",  
            dateType:"json",  
            url:getPath()+"/user",  
            data:{},  
            success:function (msg){  
                table.text('');  
                table.append('<tr><th colspan="4">使用者列表</th></tr><tr><td>id</td><td>姓名</td> <td>年齡</td><td>操作</td> </tr>')  
                for (let i = 0; i <msg.length; i++) {  
                    let tr = '<tr><td>'+msg[i].id+'</td><td>'+msg[i].name+'</td><td>'+msg[i].age+'</td><td><a href="#" class="upd">修改</a>&nbsp;&nbsp;&nbsp;<a href="#" class="del">刪除</a></tr>';  
                    table.append(tr);  
                }  
                aDelClick();  
                aUpdClick();  
            }  
        })  
    }  
    $("#btn1").click(function (){  
        getUserList();  
    })  
    $("#btn2").click(function (){  
        window.location = getPath()+'/addUser';  
    })  
    $(function (){  
        getUserList();  
    })  
</script>  
</html>

user.jsp

<%@ page import="com.zh.pojo.User" %>  
<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>Title</title>  
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>  
</head>  
<style type="text/css">  
    h1{  
        text-align: center;  
    }  
    #btn{  
        margin: 0 auto;  
        margin-top: 10px;  
        margin-left: 100px;  
    }  
    #out{  
        margin: 0 auto;  
        margin-left: 650px;  
    }  
</style>  
<body>  
<div>  
    <h1>修改使用者</h1>  
    <div id="out">  
        <form action="/RESTFul/user" method="post">  
            <%  
                User user = (User) request.getAttribute("user");  
            %>  
            姓名:<input type="text" name="username" id="username" value="<%=user.getName()%>"><br>  
            年齡:<input type="text" name="age" id="age" value="<%=user.getAge()%>"><br>  
            <div>                <button type="button" id="btn1">修改</button>  
                <button type="button" id="btn2">返回</button>  
            </div>        </form>    </div></div>  
</body>  
<script>  
    function  getPath(){  
        let pathName = document.location.pathname;  
        let index = pathName.substr(1).indexOf("/");  
        let result = pathName.substr(0,index+1);  
        return result;  
    }  
    $('#btn1').click(function (){  
        var id = <%=user.getId()%>;  
        var username = $('#username').val();  
        var age = $('#age').val();  
        $.ajax({  
            type:'post',  
            dataType:'text',  
            url:getPath()+'/user',  
            data:{  
                _method:'put',  
                id:id,  
                username:username,  
                age:age  
            },  
            success:function (){  
               window.location = getPath()+'/UserList';  
            }  
        })  
    })  
    $('#btn2').click(function (){  
        window.location = getPath()+'/UserList'  
    })  
</script>  
</html>

adduser.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>Title</title>  
</head>  
<style type="text/css">  
    h1{  
        text-align: center;  
    }  
    #submit{  
        margin: 0 auto;  
        margin-top: 10px;  
        margin-left: 100px;  
    }  
    #out{  
        margin: 0 auto;  
        margin-left: 650px;  
    }  
</style>  
<body>  
<div>  
    <h1>增加使用者</h1>  
    <div id="out">  
        <form action="/RESTFul/user" method="post">  
            姓名:<input type="text" name="username"><br>  
            年齡:<input type="text" name="age"><br>  
            <div>                <input type="submit" align="center" id="submit">  
            </div>        </form>    </div></div>  
</body>  
</html>

index

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>Title</title>  
</head>  
<body>  
    <h1>首頁</h1>  
    <h4><a href="/RESTFul/UserList">使用者列表</a></h4>  
</body>  
</html>

5、功能說明

查詢所有使用者

控制器對映的url是/user,請求方式是get,返回dao類中的集合的json字串,前端頁面一開始就傳送一次Ajax請求,收到資料之後執行回撥方法,根據JSON陣列的長度動態的新增進表格(拼接標籤),併為每個操作的a標籤新增一個點選事件(點選修改之後,js程式碼獲取點選時這一使用者的id,然後重定向到此url,例如/user/1,點選刪除之後,獲取到點選時這一使用者的id,傳送Ajax請求到伺服器,請求刪除使用者,然後重定向到列表頁面重新整理)

查詢一個使用者

url是/user/{id},get請求,符合的url將會執行這個方法,控制器拿到id之後,在dao層中獲取到此使用者的資訊,然後把使用者資訊新增到請求域中,然後返回檢視,前端中可以獲取到這個資料

增加使用者

點選增加使用者之後,會跳轉到增加使用者的介面,url是/addUser,點選修改之後,傳送Ajax請求,url是/user,請求方式:post,控制器接收到資訊之後會運算元據,然後返回操作狀態,前端根據返回的結果提示資訊和進行跳轉回使用者列表

刪除使用者

點選刪除按鈕之後,會傳送一個Ajax請求,請求引數包含_method和id,請求url: /user,_methon值為delet,控制器收到資料執行刪除使用者操作,刪除完成前端進行跳轉會使用者列表

修改使用者

點選修改使用者之後,會重定向到要修改的使用者的介面,例如/user/1,控制器已經將引數攜帶進請求中,可以進行回顯,點選修改之後,提交使用者資訊,前端控制重定向回列表頁面

至此,完成了RESTful風格的增刪改查

url 操作 請求方式
/user 查詢所有使用者 get
/user/1 查詢一個使用者 get
/user 增加使用者 post
/user 刪除使用者 delete
/user 修改使用者 put

9、SpringMVC攔截器

請求---->過濾器--->前端控制器--->控制器

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

SpringMVC中的攔截器需要實現HandlerInterceptor

SpringMVC的攔截器必須在SpringMVC的配置檔案中進行配置:

package net.biancheng.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class TestInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("afterCompletion方法在控制器的處理請求方法執行完成後執行,即檢視渲染結束之後執行");

    }

    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle方法在控制器的處理請求方法呼叫之後,解析檢視之前執行");
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle方法在控制器的處理請求方法呼叫之前執行");
        return false;
    }
}

上述攔截器的定義中實現了 HandlerInterceptor 介面,並實現了介面中的 3 個方法,說明如下。

  • preHandle( ):該方法在控制器的處理請求方法前執行,其返回值表示是否中斷後續操作,返回 true 表示繼續向下執行,返回 false 表示中斷後續操作。
  • postHandle( ):該方法在控制器的處理請求方法呼叫之後、解析檢視之前執行,可以透過此方法對請求域中的模型和檢視做進一步的修改。
  • afterCompletion( ):該方法在控制器的處理請求方法執行完成後執行,即檢視渲染結束後執行,可以透過此方法實現一些資源清理、記錄日誌資訊等工作。

1、攔截器的配置

讓自定義的攔截器生效需要在 Spring MVC 的配置檔案中進行配置,配置示例程式碼如下:

<!-- 配置攔截器 -->
<mvc:interceptors>
    <!-- 配置一個全域性攔截器,攔截所有請求 -->
    <bean class="net.biancheng.interceptor.TestInterceptor" /> 
    <mvc:interceptor>
        <!-- 配置攔截器作用的路徑 -->
        <mvc:mapping path="/**" />
        <!-- 配置不需要攔截作用的路徑 -->
        <mvc:exclude-mapping path="" />
        <!-- 定義<mvc:interceptor>元素中,表示匹配指定路徑的請求才進行攔截 -->
        <bean class="net.biancheng.interceptor.Interceptor1" />
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 配置攔截器作用的路徑 -->
        <mvc:mapping path="/gotoTest" />
        <!-- 定義在<mvc:interceptor>元素中,表示匹配指定路徑的請求才進行攔截 -->
        <bean class="net.biancheng.interceptor.Interceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

在上述示例程式碼中,元素說明如下。

  • <mvc:interceptors>:該元素用於配置一組攔截器。
  • <bean>:該元素是 mvc:interceptors 的子元素,用於定義全域性攔截器,即攔截所有的請求。
  • mvc:interceptor:該元素用於定義指定路徑的攔截器。
  • mvc:mapping:該元素是 mvc:interceptor 的子元素,用於配置攔截器作用的路徑,該路徑在其屬性 path 中定義。path 的屬性值為/**時,表示攔截所有路徑,值為/gotoTest時,表示攔截所有以/gotoTest結尾的路徑。如果在請求路徑中包含不需要攔截的內容,可以透過 mvc:exclude-mapping 子元素進行配置。

需要注意的是,mvc:interceptor 元素的子元素必須按照 mvc:mapping.../mvc:exclude-mapping.../、<bean.../> 的順序配置。

對於路徑:

/**的意思是所有資料夾及裡面的子資料夾
/*是所有資料夾,不含子資料夾
/是web專案的根目錄

2、示例

<mvc:interceptors>  
    <mvc:interceptor>  
        <mvc:mapping path="/**"/>  
        <mvc:exclude-mapping path="/user"/>  
        <mvc:exclude-mapping path="/UserList"/>  
        <mvc:exclude-mapping path="/"/>  
        <bean class="com.zh.interceptor.MyInterceptor"/>  
    </mvc:interceptor>  
</mvc:interceptors>

配置了這個攔截器之後,對訪問UserList頁面和獲取User資料的請求以及首頁都不攔截

public class MyInterceptor implements HandlerInterceptor {  
    private Date date;  
    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("控制器執行前");  
        System.out.println(simpleDateFormat.format(new Date())+request.getRequestURI());  
        return true;    }  
}

當有多個攔截器時,順序和配置的順序有關

<mvc:interceptors>  
	<bean class="com.zh.interceptor.Interceptor1"/>  
	<bean class="com.zh.interceptor.Interceptor2"/>  
</mvc:interceptors>
preHandle:攔截器--->1
preHandle:攔截器--->2
postHandle:攔截器--->2
postHandle:攔截器--->1
afterCompletion:攔截器--->2
afterCompletion:攔截器--->1

當第一個攔截器攔截時

preHandle:攔截器--->1

當後面的攔截器攔截請求時

preHandle:攔截器--->1
preHandle:攔截器--->2
afterCompletion:攔截器--->1

10、異常處理器

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

SpringMVC中,處理異常通常有4種方式

@ExceptionHandler 註解

使用 @ExceptionHandler 註解實現異常處理
使用該註解有一個不好的地方就是:進行異常處理的方法必須與出錯的方法在同一個Controller裡面。使用如下:

@Controller
public class GlobalController {

   /**
     * 用於處理異常的
     * @return
     */
    @ExceptionHandler({MyException.class})
    public String exception(MyException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return "exception";
    }

    @RequestMapping("test")
    public void test() {
        throw new MyException("出錯了!");
    }
}

@ControllerAdvice+@ExceptionHandler

使用 @ControllerAdvice+@ExceptionHandler處理全域性異常
@ExceptionHandler只能和發生異常的控制器在一起才能使用,也就是隻能處理該控制器內的方法所丟擲的異常,SpringMVC 3.2 新特性@ControllerAdvice是控制器增強,也就是專門處理異常的控制器,也就可以實現全域性的異常處理,使用方法:

@ControllerAdvice  
public class ExceptionController {  
    @ExceptionHandler(Exception.class)  
    public String ExceptionHandle(Exception ex, Model model){  
        model.addAttribute("ex",ex);  
        return "error";  
    }  
}

異常處理介面 HandlerExceptionResolver

實現 Spring 的異常處理介面 HandlerExceptionResolver,自定義自己的異常處理器。

public class MyExceptionHandler implements HandlerExceptionResolver {  
    @Override  
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {  
  
        if (ex instanceof ArithmeticException){  
            ModelAndView arithmeticException = new ModelAndView();  
            arithmeticException.setViewName("ArithmeticException");  
            return arithmeticException;  
        }else if(ex instanceof NullPointerException){  
            ModelAndView nullPointerException = new ModelAndView();  
            nullPointerException.setViewName("NullPointerException");  
            return nullPointerException;  
        }else {  
            ModelAndView error = new ModelAndView();  
            error.setViewName("error");  
            return error;  
        }  
    }  
}

然後到SpringMVC配置檔案中註冊這個bean

<bean class="com.zh.exception.MyExceptionHandler"/>

可以看到原始碼中的異常處理器中已經有了我們自己定義的異常處理器

然後遍歷這些異常處理器,異常處理器處理完成之後會返回檢視,如果檢視為空,則證明這個異常處理器無法處理這個異常,繼續遍歷,找到可以處理這個異常的異常處理器之後,返回結果,進行檢視解析

SimpleMappingExceptionResolver

使用 Spring MVC 提供的簡單異常處理器 SimpleMappingExceptionResolver。
全域性異常處理可使用 SimpleMappingExceptionResolver 來實現。它將異常類名對映為檢視名,即發生異常時使用對應的檢視報告異常。

使用方法,直接在配置檔案註冊bean就行

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
    <!--異常沒有被特殊處理時,預設的檢視名字-->  
    <property name="defaultErrorView" value="error"/>  
    <!--異常資訊的引數名字,可以根據這個取出異常資訊-->  
    <property name="exceptionAttribute" value="ex"/>  
    <!-- 定義需要特殊處理的異常,用類名或完全路徑名作為key,異常頁名作為值 -->  
    <property name="exceptionMappings">  
        <props>            
	        <prop key="java.lang.ArithmeticException">ArithmeticException</prop>
	        <prop key="java.lang.NullPointerException">NullPointerException</prop>  
        </props>    
	</property>
</bean>

可以看到,註冊的這個異常處理器也被載入進來了

11、關於檔案的上傳和下載


1、檔案上傳

檔案上傳需要用到org.springframework.web.multipart.commons.CommonsMultipartResolver這個類,這個類極大簡便了我們對檔案操作

檔案上傳是專案開發中最常見的功能。為了能上傳檔案,必須將表單的method設定為POST,並將enctype設定為multipart/form-data。一旦設定了enctype為multipart/form-data,瀏覽器即會採用二進位制流的方式來處理表單資料,只有在這樣的情況下,瀏覽器才會把使用者選擇的檔案以二進位制資料傳送給伺服器。

Spring MVC 為檔案上傳提供了直接的支援,這種支援是用即插即用的 MultipartResolver 實現的。Spring MVC 使用 Apache Commons FileUpload技術實現了一個 MultipartResolver 實現類:CommonsMultipartResolver 。因此,SpringMVC的檔案上傳還需要依賴Apache Commons FileUpload的元件。

<dependency>  
    <groupId>commons-fileupload</groupId>  
    <artifactId>commons-fileupload</artifactId>  
    <version>1.4</version>  
</dependency>

若沒有這匯入依賴,則報錯

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'multipartResolver':
Failed to introspect bean class [org.springframework.web.multipart.commons.CommonsMultipartResolver] for lookup method metadata:
could not find class that it depends on; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory

SpringMVC上下文中預設沒有裝配 MultipartResolver,因此預設情況下其不能處理檔案上傳工作。如果想使用Spring的檔案上傳功能,則需要在上下文中配置 MultipartResolver。

MultipartResolver 用於處理檔案上傳,當收到請求時 DispatcherServlet 的 checkMultipart() 方法會呼叫 MultipartResolver 的 isMultipart() 方法判斷請求中是否包含檔案。如果請求資料中包含檔案,則呼叫 MultipartResolver 的 resolveMultipart() 方法對請求的資料進行解析,然後將檔案資料解析成 MultipartFile 並封裝在 MultipartHttpServletRequest (繼承了 HttpServletRequest) 物件中,最後傳遞給 Controller

<!--配置檔案上傳相關-->
2 <bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
3     <!-- 設定檔案上傳的最大值-->
4     <property name="maxUploadSize" value="10485760"></property>
5     <!-- 設定檔案上傳時寫入記憶體的最大值,如果小於這個引數不會生成臨時檔案,預設為10240 -->
6     <property name="maxInMemorySize" value="4096"></property>
7     <!-- 設定預設編碼 -->
8     <property name="defaultEncoding" value="UTF-8"></property>
9 </bean>

這個類還有很多引數設定,根據需求設定

上傳檔案的頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head>  
    <title>Title</title>  
</head>  
<body>  
<form action="/RESTFul/upload" method="post" enctype="multipart/form-data">  
    檔案說明:<input type="text" name="wjsm" /><br/>  
    附件:<input type="file" name="file" /><br/>  
    <input type="submit" value="提交"/>  
</form>  
  
</body>  
</html>

編寫Controller以及處理方法:測試ok

MultipartFile 封裝了請求資料中的檔案,此時這個檔案儲存在記憶體中或臨時的磁碟檔案中,需要將其轉存到一個合適的位置,因為請求結束後臨時儲存將被清空。在 MultipartFile 介面中有如下方法:

  • String getName(); // 獲取引數的名稱
  • String getOriginalFilename(); // 獲取檔案的原名稱
  • String getContentType(); // 檔案內容的型別
  • boolean isEmpty(); // 檔案是否為空
  • long getSize(); // 檔案大小
  • byte[] getBytes(); // 將檔案內容以位元組陣列的形式返回
  • InputStream getInputStream(); // 將檔案內容以輸入流的形式返回
  • void transferTo(File dest); // 將檔案內容傳輸到指定檔案中
@RequestMapping("/upload")  
public String fileUpload(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws IOException {  
  
    //獲取儲存該檔案的資料夾在伺服器上的真實路徑,會自動拼接上  
    String realPath = request.getSession().getServletContext().getRealPath("/resource/userdata");  
  
    //獲取檔案原來的名字  
    String fileName = file.getOriginalFilename();  
  
    //建立一個File,此file會自動拼接路勁,就是這個檔案下載之後儲存到伺服器上的路徑  
    File desFile = new File(realPath,fileName);  
  
    //這個檔案如果為空,則建立一個  
    if (!desFile.exists()){  
        desFile.mkdirs();  
    }  
    //開始往這個檔案裡面寫資料(複製)  
    file.transferTo(desFile);  
  
    return "success";  
}

2、檔案下載

SpringMVC提供了一個 ResponseEntity 型別,使用它可以很方便地定義返回的 HttpHeaders 和HttpStatus 。

@RequestMapping("/filedownload/{fileName}")  
public ResponseEntity<byte[]> fileDownload(@PathVariable String fileName, HttpSession session) throws IOException {  
      
    //獲取檔案的真實路徑  
    String path = session.getServletContext().getRealPath("/resource/userdata");  
    File file = new File(path,fileName);  
  
    //建立一個對該檔案的輸入流  
    InputStream inputStream = new FileInputStream(file);  
  
    //建立一個和檔案大小一樣的byte陣列  
    byte[] bytes = new byte[inputStream.available()];  
  
    //把資料讀取到該陣列  
    inputStream.read(bytes);  
  
    //建立響應報文的響應頭  
    HttpHeaders httpHeaders = new HttpHeaders();  
  
    //設定瀏覽器處理該響應的方式,attachment是附件形式,後面跟客戶端下載該檔案的名字  
    //如果客戶端下載檔案會出現名字亂碼,則應轉換一下編碼  
    //httpHeaders.add("Content-Disposition", "attachment;filename="+ URLEncoder.encode(fileName,"utf-8"));  
    httpHeaders.add("Content-Disposition", "attachment;filename="+ fileName);  

	//application/octet-stream: 二進位制流資料(最常見的檔案下載)  
	httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
	
    //設定響應狀態  
    HttpStatus httpStatus = HttpStatus.OK;  
  
    return new ResponseEntity<byte[]>(bytes,httpHeaders,httpStatus);  
}

12、註解配置SpringMVC

完全使用配置類和註解代替web工程的配置檔案web.xml和SpringMVC、Spirng的xml配置檔案的功能。
在Servlet3.0環境中,容器會在類路徑中查詢實現javax.servlet.ServletContainerInitializer介面的類,如果找到的話就用它來配置Servlet容器。
Spring提供了這個介面的實現,名為SpringServletContainerInitializer,這個類反過來又會查詢實現WebApplicationInitializer的類並將配置的任務交給它們來完成。Spring3.2引入了一個便利的WebApplicationInitializer基礎實現,名為AbstractAnnotationConfigDispatcherServletInitializer,當我們的類擴充套件了AbstractAnnotationConfigDispatcherServletInitializer並將其部署到Servlet3.0容器的時候,容器會自動發現它,並用它來配置Servlet上下文。

編寫WebConfig

  • WebConfig.class用來代替web工程配置檔案web.xml,
  • 繼承了AbstractAnnotationConfigDispatcherServletInitializer.
package com.zh.config;  
  
import com.zh.filter.MyFilter;  
import org.springframework.web.filter.CharacterEncodingFilter;  
import org.springframework.web.filter.HiddenHttpMethodFilter;  
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;  
  
import javax.servlet.Filter;  
  
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {  
    /**  
     * 指定Spring的配置類  
     * @return  
     */  
    @Override  
    protected Class<?>[] getRootConfigClasses() {  
        return new Class[0];  
    }  
  
  
    /**  
     * 指定SpringMVC的配置類  
     * @return  
     */  
    @Override  
    protected Class<?>[] getServletConfigClasses() {  
        return new Class[]{SpringMVCConfig.class};  
    }  
  
    /**  
     * 指定DispatcherServlet的對映規則,即url-pattern  
     * @return  
     */  
    @Override  
    protected String[] getServletMappings() {  
        return new String[]{"/"};  
    }  
  
  
    /**  
     *這裡配置的過濾器是和DispatcherServlet一樣,  
     * 也就是所有經過DispatcherServlet的都會經過這些過濾器  
     * 如果需要增加自己定義的過濾器,則可以使用註解@WebFilter  
     * @return  
     */  
  
    @Override  
    protected Filter[] getServletFilters() {  
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();  
        characterEncodingFilter.setEncoding("utf-8");   
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();  
        MyFilter myFilter = new MyFilter();  
        return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter,myFilter};  
    }  
}

以上配置過濾器的過濾路徑是和DispatcherServlet的對映規則一樣,如果還需要新增自己的過濾去,則需要註解配置路徑
注:@WebFilter 註解是 servlet3.0 提供的註解,另外
關於匯入serv依賴的時候出現的問題
servlet.jar 是servlet 3.0 版本之前的地址
javax.servlet-api.jar 是servlet 3.0 版本之後的地址

此配置類對應了web.xml

<?xml version="1.0" encoding="UTF-8"?>  
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"  
         version="4.0">  
  
    <filter>        
	    <filter-name>characterEncodingFilter</filter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>            
	        <param-name>encoding</param-name>  
            <param-value>utf-8</param-value>  
        </init-param>        
        <init-param>            
	        <param-name>forceResponseEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>    
	</filter>    
	<filter-mapping>        
		<filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
    <filter>        
	    <filter-name>HiddenHttpMethodFilter</filter-name>  
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>  
    </filter>    
    <filter-mapping>        
	    <filter-name>HiddenHttpMethodFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
  
    <servlet>        
	    <servlet-name>dispatcherServlet</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>            
	        <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:SpringMVC.xml</param-value>  
        </init-param>    
	</servlet>    
	<servlet-mapping>        
		<servlet-name>dispatcherServlet</servlet-name>  
        <url-pattern>/</url-pattern>  
    </servlet-mapping>  
  
</web-app>

配置了一個servlet(dispatcherServlet)和兩個過濾器characterEncodingFilter,HiddenHttpMethodFilter

編寫SpringMVCConfig

需要配置如下內容
包掃描
註解驅動
靜態資源處理
檢視解析器
攔截器
異常處理器
上傳檔案解析器
訊息轉換器
view-controller

package com.zh.config;  
  
import com.zh.interceptor.MyInterceptor;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.http.MediaType;  
import org.springframework.http.converter.HttpMessageConverter;  
import org.springframework.http.converter.StringHttpMessageConverter;  
import org.springframework.web.multipart.commons.CommonsMultipartResolver;  
import org.springframework.web.servlet.HandlerExceptionResolver;  
import org.springframework.web.servlet.ViewResolver;  
import org.springframework.web.servlet.config.annotation.*;  
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;  
import org.springframework.web.servlet.view.InternalResourceViewResolver;  
  
import java.nio.charset.Charset;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Properties;  
  
@Configuration  
//包掃描  
@ComponentScan("com.zh")  
//開啟註解驅動  
@EnableWebMvc  
public class SpringMVCConfig implements WebMvcConfigurer {  
  
    //靜態資源處理  
    @Override  
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {  
        configurer.enable();  
    }  
  
    //檢視解析器  
    @Bean  
    public ViewResolver viewResolver(){  
        InternalResourceViewResolver resourceViewResolver = new InternalResourceViewResolver();  
        resourceViewResolver.setPrefix("/WEB-INF/jsp/");  
        resourceViewResolver.setSuffix(".jsp");  
        return resourceViewResolver;  
    }  
  
    //配置攔截器  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");  
    }  
  
    //配置異常對映  
    @Override  
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {  
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();  
        Properties prop = new Properties();  
        prop.setProperty("java.lang.ArithmeticException", "error");  
        //設定異常對映  
        exceptionResolver.setExceptionMappings(prop);  
        //設定共享異常資訊的鍵  
        exceptionResolver.setExceptionAttribute("ex");  
  
        resolvers.add(exceptionResolver);  
    }  
  
    //檔案上傳解析器,需要其他依賴commons-fileupload  
    @Bean  
    public CommonsMultipartResolver multipartResolver(){  
        return new CommonsMultipartResolver();  
    }  
  
    //配置訊息轉換器  
    @Override  
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {  
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();  
  
        //設定應用型別和字元編碼  
        List<MediaType> mediaTypeList = new ArrayList<>();  
        mediaTypeList.add(new MediaType(MediaType.APPLICATION_JSON,Charset.forName("utf-8")));  
        mediaTypeList.add(new MediaType(MediaType.TEXT_HTML,Charset.forName("utf-8")));  
  
        //設定次轉換器支援的型別  
        stringHttpMessageConverter.setSupportedMediaTypes(mediaTypeList);  
        converters.add(stringHttpMessageConverter);  
    }  
  
    //相當於<mvc:view-controller path="/" view-name="index"/>  
    @Override  
    public void addViewControllers(ViewControllerRegistry registry) {  
        registry.addViewController("/").setViewName("index");  
    }  
}

13、SpringMVC執行流程

1、SpringMVC常用元件

  • DispatcherServlet:前端控制器,不需要工程師開發,由框架提供

作用:統一處理請求和響應,整個流程控制的中心,由它呼叫其它元件處理使用者的請求

  • HandlerMapping:處理器對映器,不需要工程師開發,由框架提供

作用:根據請求的url、method等資訊查詢Handler,即控制器方法

  • Handler:處理器,需要工程師開發

作用:在DispatcherServlet的控制下Handler對具體的使用者請求進行處理

  • HandlerAdapter:處理器介面卡,不需要工程師開發,由框架提供

作用:透過HandlerAdapter對處理器(控制器方法)進行執行

  • ViewResolver:檢視解析器,不需要工程師開發,由框架提供

作用:進行檢視解析,得到相應的檢視,例如:ThymeleafView、InternalResourceView、RedirectView

  • View:檢視

作用:將模型資料透過頁面展示給使用者

2、DispatcherServlet呼叫元件處理請求

a>processRequest()

FrameworkServlet重寫HttpServlet中的service()和doXxx(),這些方法中呼叫了processRequest(request, response)

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

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
		// 執行服務,doService()是一個抽象方法,在DispatcherServlet中進行了重寫
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}
b>doService()

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

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
        requestPath = ServletRequestPathUtils.parseAndCache(request);
    }

    try {
        // 處理請求和響應
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (requestPath != null) {
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }
    }
}
c>doDispatch()

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

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /*
            	mappedHandler:呼叫鏈
                包含handler、interceptorList、interceptorIndex
            	handler:瀏覽器傳送的請求所匹配的控制器方法
            	interceptorList:處理控制器方法的所有攔截器集合
            	interceptorIndex:攔截器索引,控制攔截器afterCompletion()的執行
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
           	// 透過控制器方法建立相應的處理器介面卡,呼叫所對應的控制器方法
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
			
            // 呼叫攔截器的preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 由處理器介面卡呼叫具體的控制器方法,最終獲得ModelAndView物件
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            applyDefaultViewName(processedRequest, mv);
            // 呼叫攔截器的postHandle()
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 後續處理:處理模型資料和渲染檢視
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}
d>processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 處理模型資料和渲染檢視
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // 呼叫攔截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

3、SpringMVC的執行流程

此圖自己總結的:

  1. 使用者向伺服器傳送請求,請求被SpringMVC 前端控制器 DispatcherServlet捕獲。

  2. DispatcherServlet對請求URL進行解析,得到請求資源識別符號(URI),判斷請求URI對應的對映:

  • 不存在

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

    ii. 如果沒配置,則控制檯報對映查詢不到,客戶端展示404錯誤

    iii. 如果有配置,則訪問目標資源(一般為靜態資源,如:JS,CSS,HTML),找不到客戶端也會展示404錯誤

  • 存在則執行下面的流程

  1. 根據該URI,呼叫HandlerMapping獲得該Handler配置的所有相關的物件(包括Handler物件以及Handler物件對應的攔截器),最後以HandlerExecutionChain執行鏈物件的形式返回。

  2. DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter。

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

  4. 提取Request中的模型資料,填充Handler入參,開始執行Handler(Controller)方法,處理請求。在填充Handler的入參過程中,根據你的配置,Spring將幫你做一些額外的工作:

    • HttpMessageConveter: 將請求訊息(如Json、xml等資料)轉換成一個物件,將物件轉換為指定的響應資訊

    • 資料轉換:對請求訊息進行資料轉換。如String轉換成Integer、Double等

    • 資料格式化:對請求訊息進行資料格式化。 如將字串轉換成格式化數字或格式化日期等

    • 資料驗證: 驗證資料的有效性(長度、格式等),驗證結果儲存到BindingResult或Error中

  5. Handler執行完成後,向DispatcherServlet 返回一個ModelAndView物件。

  6. 此時將開始執行攔截器的postHandle(...)方法【逆向】。

  7. 根據返回的ModelAndView(此時會判斷是否存在異常:如果存在異常,則執行HandlerExceptionResolver進行異常處理)選擇一個適合的ViewResolver進行檢視解析,根據Model和View,來渲染檢視。

  8. 渲染檢視完畢執行攔截器的afterCompletion(…)方法【逆向】。

  9. 將渲染結果返回給客戶端。

相關文章