一篇文章學會springMVC(轉)

說在前面

本文只是入門
爲何用springMVC?springMVC有什麼有缺點?springMVC和Struts有什麼區別?等等這些問題能夠參考網路上資源,本文的重點是快速帶入,讓你們瞭解熟悉springMVC。springMVC畢竟是工具,工具的特色就是熟能生巧,經過快速掌握,多加練習、解決問題及概括總結確定能夠掌握而且成爲本身的東西。css

簡單描述

springMVC主要是經過前端控制器controller中的註解來完成請求處理的。前端不管是以何種方式請求,都會經過controller進行輕度處理、轉發以及調度後端的處理器進行處理,最後返回正確的視圖及響應。以此來看,springMVC說白了既能夠返回合適的頁面,也能夠響應RESTful請求。html

工做流程

以springMVC源碼爲導向

這裏寫圖片描述

以springMVC工做流爲導向

這裏寫圖片描述

springMVC在項目中的位置(以maven爲例)

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
src |-main |---|---java |---|-----|----package.controller (springMVC中controller類) |---|-----|----package.interceptor (springMVC中的攔截器) |---|-----|----package.service (dao,po,jo,vo,commons,utils) |---|---resource |---|-----|----applicationContext-*.xml |---|-----|----springmvc.xml (springMVC配置文件) |---|-----|----messages.properties (springMVC消息屬性文件) |---|---webapp |---|----|----pages(springMVC視圖頁面) |---|----|----WEB-INF |---|----|-----|---web.xml(springMVC的DispatcherServlet及相關配置) |--external libraries (springMVC依賴庫)

HELLO WORLD

第一步,建立web工程

國際慣例,先搭建一個spring項目,經過實戰的方式,漸進的學習,杜絕眼高手低的學習方式。
若是是maven須要archetype爲maven-archetype-webapp。前端

第二步,導入依賴庫

附上pom.xml,非maven項目可用導入相應的jar包java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>xhsTest</groupId> <artifactId>spring-mvc-test</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>spring-mvc-test Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <webroot.path>src/main/webapp</webroot.path> <spring-version>4.1.0.RELEASE</spring-version> <log4j-version>2.2</log4j-version> </properties> <build> <finalName>springmvctest</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <webXml>src/main/webapp/WEB-INF/web.xml</webXml> </configuration> </plugin> <!-- 添加jetty --> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>8.1.8.v20121106</version> <configuration> <reload>manual</reload> <webAppConfig> <contextPath>/</contextPath> </webAppConfig> <connectors> <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector"> <port>8080</port> </connector> </connectors> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- web servlet --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.4</version> </dependency> <!-- miscellaneous spring驗證依賴庫--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> </dependencies> </project>

第三步,配置web.xml加載spring到容器中

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>springMVC-test</display-name> <!--springMVC:建立DispatcherServlet--> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--DispatcherServlet初始化參數,配置springmvc配置文件的位置和名字--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--在建立web應用且web應用未加載的適合建立,而不是第一次請求的時候建立--> <load-on-startup>1</load-on-startup> </servlet> <!--攔截全部請求--> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

第四步,配置springmvc.xml配置文件

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
<?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 http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <!--配置自動掃描的包,將bean加入spring容器--> <context:component-scan base-package="com.iboray.smt"></context:component-scan> <!--配置視圖解析器--> <bean id="resolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--返回前綴--> <property name="prefix" value="/"></property> <!--後綴--> <property name="suffix" value=".jsp"></property> </bean> </beans>

第五步,編寫第一個controller(UserController)

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
package com.iboray.smt.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Controller 標識該類爲Controller * RequestMapping value爲請求根路徑 */ @Controller @RequestMapping(value = "/user") public class UserController { /** * RequestMapping中value定義該方法的請求地址爲userInfo,method定義請求方式爲GET方式 * 最終訪問地址爲: 根路徑 / 請求地址.也就是/user/userInfo * * @return 返回地址爲視圖解析器的前綴+返回值+後綴.也就是/userInfo.jsp */ @RequestMapping(value = "userInfo",method = RequestMethod.GET) public String getUserInfo(){ return "userInfo"; } }

第六步,編寫請求的jsp

附上兩個jsp,一個是測試發送請求,一個是controller響應的視圖jsp
index.jpios

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
<html> <body> <h2>Hello World!</h2> <hr> <a href="/user/userInfo" >get UserInfo</a> </body> </html>

userInfo.jspweb

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>user info</title> </head> <body> <h2>This is user info page</h2> </body> </html>

進階

經過以上6步,最簡單的springMVC就搭建好了。接下來,咱們就能夠經過練習springmvc中各技術揭開她的神祕面紗。
以後全部練習都經過index.jsp和UserController.Java完成。spring

練習1:RequestMapping映射請求參數、請求方法或請求頭

RequestMapping爲控制器指定能夠處理哪些請求的URL,在控制器的類定義和方法定義處均可以修飾。
- 類定義處:提供初步的請求映射信息,至關於web應用的根目錄
- 方法定義出:提供進一步的細分映射信息,相對於類定義處的URL,若是類定義處未標註@RequestMapping,那麼方法定義處就相對於web應用的根目錄。
DispatchServlet截獲請求後,就經過控制器上@RequestMapping提供的映射信息肯定對應的處理方法
RequestMapping除了可使用請求URL外,還能夠經過請求參數,請求方法,請求頭映射請求
requestMapping的value,method,params和heads分別表示,請求URL,請求方法,請求參數和請求頭的映射條件,他們之間是與的關係,聯合使用多個條件,可使請求映射更加精準。
params和heads支持簡單的表達式
1. param1:表示請求必須包含名爲param1的請求參數
2. !param1:表示請求不能包含名爲param1的請求參數
3. param1 != value1:表示請求必須包含名爲param1的請求參數,且值不等於value1
4. {「param1 == value1」,」param2」}:請求必須包含名爲param1和param2的參數,且param1的值必須等於value1
練習
index.jspexpress

  
  
  
  
  • 1
  • 1
<a href="/user/userInfo?name=xxx&age=20" >RequestMapping params</a>

UserController.javaapache

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "userInfo",method = RequestMethod.GET,params = {"name=xxx","age"}) public String getUserInfo(){ return "userInfo"; }

練習2:PathVariable映射URL綁定佔位符

帶URL佔位符是Spring3.0新增功能,該功能在springmvc向REST目標挺進發展過程當中具備里程碑的意義。
經過@PathVariable能夠將URL中佔位符參數綁定到控制器處理的方法的入參中,也就是說URL中的{paramName}佔位符能夠經過@PathVariable(paramName)綁定到操做方法的入參中。
index.jspjson

  
  
  
  
  • 1
  • 1
<a href="/user/delUser/3" >PathVariable_delUser</a>

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/delUser/{delId}",method = RequestMethod.GET) public String delUser(@PathVariable(value = "delId") Integer id){ System.out.println("delId : "+id); return SUCCESS; }

練習3:REST請求

關於更多REST請參考以下兩篇文章
理解本真REST架構
深刻淺出REST
SpringMVC經過配置HiddenHttpMethodFilter來支持PUT/DELETE請求
1 web.xml增長以下配置

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<!--可以讓springMVC 將POST請求轉爲PUT/DELETE等..--> <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>

2 發送POST請求,並攜帶name=_method的隱藏域轉爲DELETE或PUT等請求
index.jsp

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
<form id="putUserForm" method="post" action="/user/putUser/5"> <input type="hidden" name="_method" value="PUT"> <input type="submit" value="submit"> </form>

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/putUser/{putId}",method = RequestMethod.PUT) public String putUser(@PathVariable(value = "putId") Integer id){ System.out.println("putId : "+id); return SUCCESS; }

練習4:請求處理方法簽名@RequestParam

SpringMVC經過分析處理方法的簽名,將HTTP請求的信息綁定處處理方法的入參中,並根據方法的返回類型作出相應的後續處理。SpirngMVC對控制器處理方法的限制很寬鬆,必要時能夠對方法及方法入參標註註解(@PathVariable@RequestParam@RequestHeader)
使用RequestParam來映射參數
1. value 請求參數名
2. required 參數是否必須,默認爲True
3. defaultValue 參數默認值
index.jsp

  
  
  
  
  • 1
  • 1
<a href="/user/queryUser?userId=9&name=Tim" >RequestParam_queryUser</a>

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/queryUser",method = RequestMethod.GET) public String queryUser(@RequestParam(value = "userId") Integer id,@RequestParam(value = "name",required = false,defaultValue = "") String name){ System.out.println("queryUserId : "+id + " name : "+ name); return SUCCESS; }

練習5:使用@RequestHeader綁定請求報頭的屬性值

請求報頭包含若干屬性值,服務器可據此獲取客戶端信息,經過@RequestHeader便可將請求報頭的屬性值綁定處處理方法的入參中。
index.jsp

  
  
  
  
  • 1
  • 1
<a href="/user/userPwd" >RequestHeader_userPwd</a>

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/userPwd",method = RequestMethod.GET) public String userPwd(@RequestHeader("Accept-Encoding") String encoding){ System.out.println("encoding : "+encoding); return SUCCESS; }

練習6:使用@CookieValue綁定請求中的Cookie值

index.jsp

  
  
  
  
  • 1
  • 1
<a href="/user/testCookie" >CookieValue_testCookie</a>

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/testCookie") public String testCookie(@CookieValue(value = "JSESSIONID") String jsession){ System.out.println("jsession : "+jsession); return SUCCESS; }

練習7:使用POJO對象自動綁定參數

SpringMVC會按照請求參數名和對象屬性名進行自動綁定,自動爲該對象填充屬性值,支持級聯屬性。如dept.deptName,dept.address.tel等
請求地址

  
  
  
  
  • 1
  • 1
http://127.0.0.1:8080/user/addUser?id=1&name=Tim&dept.deptName=dev

User.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public class User { private int id; private String name; private Detp dept; getter setter toString... }

Dept.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
public class Detp { private int id; private String deptName; getter setter toString... }

後臺輸出:

  
  
  
  
  • 1
  • 1
user : User{id=1, name='Tim', dept=Detp{id=0, deptName='dev'}}

練習8:使用servletAPI做爲入參

UserController.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/testServletAPI") public String testServletAPI(HttpServletRequest request){ System.out.println("request : "+request.getRequestURI()); return SUCCESS; }

練習9:處理模型數據ModelAndView

SpringMVC提供如下幾種途徑輸出模型數據
ModelAndView:處理方法返回值類型爲ModelAndView時,方法體便可經過該對象添加模型數據。
Map 或 Model:入參爲Model、ModelMap或Map時,處理方法返回時,Map中的數據會自動添加到模型中。
@SessionAttributes:將模型中的某個數據暫時存到HttpSession中,以便多個請求之間能夠共享這個屬性。
@ModelAttribute:方法入參標書該註解後,入參的對象就會放到數據模型中。
ModelAndView
控制器返回值若是爲ModelAndView時,則其既包含視圖信息,也包含數據模型信息。
添加模型數據:
ModelAndView addObject(String attributeName,Object attributeValue);
ModelAndView addAllObject(Map《String,?》, modelMap)
設置視圖
void setView(View view);
void setViewName(String viewName);

Controller.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@RequestMapping(value = "/testModelAndView") public ModelAndView testModelAndView(){ ModelAndView mv = new ModelAndView(SUCCESS); mv.addObject("msg","testModelAndView"); return mv; }

那到底是怎樣一個運行流程呢?
跟蹤源碼主線爲:
由關鍵對象DispacherServlet最外層doDispatch進入
第一層
org.springframework.web.servlet.DispatcherServlet#doDispatch

  
  
  
  
  • 1
  • 1
this.processDispatchResult(processedRequest, response, mappedHandler, err, dispatchException);

第二層
org.springframework.web.servlet.DispatcherServlet#processDispatchResult

  
  
  
  
  • 1
  • 1
this.render(mv, request, response);

第三層
org.springframework.web.servlet.DispatcherServlet#render

  
  
  
  
  • 1
  • 1
view.render(mv.getModelInternal(), request, response);

第四層
org.springframework.web.servlet.view.AbstractView#render

  
  
  
  
  • 1
  • 1
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);

第五層
這一步比較關鍵,須要找到咱們配置的視圖解析器類型InternalResourceView(默認也是這個)
org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

  
  
  
  
  • 1
  • 1
this.exposeModelAsRequestAttributes(model, request);

第六層
org.springframework.web.servlet.view.AbstractView#exposeModelAsRequestAttributes

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
while(var3.hasNext()) { Entry entry = (Entry)var3.next(); String modelName = (String)entry.getKey(); Object modelValue = entry.getValue(); if(modelValue != null) { request.setAttribute(modelName, modelValue);//關鍵看這裏!!!看下面說明1 if(this.logger.isDebugEnabled()) { this.logger.debug("Added model object \'" + modelName + "\' of type [" + modelValue.getClass().getName() + "] to request in view with name \'" + this.getBeanName() + "\'"); } } ...

說明1:經過modelAndView的addObject設置的K/V值,最終是經過setAttribute一個個set到request請求域中。這下就明白了吧!再看modelAndView對象,包括兩個重要屬性是private Object view;private ModelMap model;,那其中的addObject方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
public ModelAndView addObject(String attributeName, Object attributeValue) { this.getModelMap().addAttribute(attributeName, attributeValue); return this; }

就是獲取modelMap,執行addAttribute方法,再進入這個方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
public ModelMap addAttribute(String attributeName, Object attributeValue) { Assert.notNull(attributeName, "Model attribute name must not be null"); this.put(attributeName, attributeValue);//關鍵看這裏 return this; }

由於modelMap繼承了public class ModelMap extends LinkedHashMap,因此put方法就是咱們經常使用的Map對象的put方法。說白了,ModelAndView中的model就是一個Map對象,搞明白這個很重要。

練習10:處理模型數據Map

SpringMVC內部使用了一個org.springframework.ui.model接口存儲模型數據
SpringMVC在調用方法前會建立一個隱含的模型對象存儲模型數據。
若是方法的入參爲Map or Model類型,SpringMVC會將隱含的模型的引用傳遞給這些入參,開發者能夠經過這個入參對象訪問到模型中的全部數據,也能夠向模型中添加新的屬性數據。
Controller.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@RequestMapping(value = "/testMap") public String testMap(Map<String,Object> map){ map.put("msg", Arrays.asList("a","b","c")); return SUCCESS; }

數據結果頁面接參msg : ${msg} 結果爲:msg : [a, b, c]

練習11:處理模型數據@SessionAtrribute

若想再多個請求之間共享某個模型屬性數據,則能夠在控制器上標註@SessionAttribute註解,SpringMVC將模型中對應的屬性暫存到HttpSession中。
@SessionAttribute除了能夠經過屬性名指定放到會話中的屬性外(value屬性),還能夠根據對象類型指定哪些類型能夠放到會話中(types屬性)。
Controller.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
@SessionAttributes(value = {"user"},types = {String.class} ) @Controller @RequestMapping(value = "/user") public class UserController { @RequestMapping(value = "/testSessionAttribute") public String testSessionAttribute(Map<String,Object> map){ map.put("user", new User(1,"Tim")); map.put("sessStr","John"); return SUCCESS; } }

練習12:@ModelAttribute修飾

我我的使用ModelAttribute的地方不多。通常的使用是修飾方法和修飾入參。修飾方法會在調用目標方法前首先調用ModelAttribute修飾的方法,修飾入參意思是獲取ModelAttribute中指定的對象名稱。
修飾方法controller.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
@ModelAttribute public void userModelAttributeModel(@RequestParam(value = "id",required = false) Integer id ,Map<String,Object> map){ System.out.println("userModelAttributeModel coming..."); if(id != null){ User u = new User(1,"Jackson","98765"); System.out.println("userModelAttributeModel : " + u); map.put("user",u); } }

修飾入參

  
  
  
  
  • 1
  • 1
public ModelAndView sendMail(HttpServletRequest request, @ModelAttribute("user") User user)

究竟什麼地方調用的ModelAttribute修飾的方法,運行流程是什麼?

源碼分析

第一步,探究@ModelAttribute修飾的方法入參Map
在修飾的方法map.put…也就是設置值的時候打斷點,進入方法後,先查看Map入參的類型爲BindingAwareModelMap,那BindingAwareModelMap 繼承 ExtendedModelMap,而 ExtendedModelMap 繼承 ModelMap 實現了 Model接口。而ModelMap 又繼承 LinkedHashMap。這個入參Map即是咱們經常使用的ModelAndView中的Model。
第一層(頂層執行[ 類 # 方法 ])我以爲有必要把整個方法附上
org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
public final Object invokeHandlerMethod(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod); try { boolean debug = logger.isDebugEnabled(); //獲取全部@SessionAttribute,並從HttpSession中獲取key對應的對象,放入implicitModel中 for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName); if (attrValue != null) { implicitModel.addAttribute(attrName, attrValue); } } //獲取全部@ModelAttribute,並從將@ModelAttribute修飾的方法中初始化的對象,放入implicitModel中 for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod); Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel); if (debug) { logger.debug("Invoking model attribute method: " + attributeMethodToInvoke); } String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value(); if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) { continue; } ReflectionUtils.makeAccessible(attributeMethodToInvoke); //這裏就是反射執行@ModelAttribute修飾的方法,執行完後implicitModel就增長了方法中添加的KV值。 Object attrValue = attributeMethodToInvoke.invoke(handler, args); if ("".equals(attrName)) { Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass()); attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue); } if (!implicitModel.containsAttribute(attrName)) { implicitModel.addAttribute(attrName, attrValue); } } //主要做用是將@SessionAttribute和@ModelAttribute賦值的implicitModel和請求參數並集操做,獲得目標方法的最終入參(implicitModel) Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); if (debug) { logger.debug("Invoking request handler method: " + handlerMethodToInvoke); } ReflectionUtils.makeAccessible(handlerMethodToInvoke); //執行目標方法,傳入參數(implicitModel) return handlerMethodToInvoke.invoke(handler, args); } catch (IllegalStateException ex) { // Internal assertion failed (e.g. invalid signature): // throw exception with full handler method context... throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex); } catch (InvocationTargetException ex) { // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception... ReflectionUtils.rethrowException(ex.getTargetException()); return null; } }

因而可知無論請求這個controller的什麼方法,都會先獲取@SessionAttribute中的KV值,並進入@ModelAttribute修飾的方法,執行invoke後進入ModelAttribute方法,執行後給implicitModel賦值,

最終目標方法的入參的具體賦值的關鍵代碼爲:
org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { Class<?>[] paramTypes = handlerMethod.getParameterTypes(); Object[] args = new Object[paramTypes.length]; for (int i = 0; i < args.length; i++) { MethodParameter methodParam = new MethodParameter(handlerMethod, i); methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); String paramName = null; String headerName = null; boolean requestBodyFound = false; String cookieName = null; String pathVarName = null; String attrName = null; boolean required = false; String defaultValue = null; boolean validate = false; Object[] validationHints = null; int annotationsFound = 0; Annotation[] paramAnns = methodParam.getParameterAnnotations(); for (Annotation paramAnn : paramAnns) { if (RequestParam.class.isInstance(paramAnn)) { RequestParam requestParam = (RequestParam) paramAnn; paramName = requestParam.value(); required = requestParam.required(); defaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); annotationsFound++; } else if (RequestHeader.class.isInstance(paramAnn)) { RequestHeader requestHeader = (RequestHeader) paramAnn; headerName = requestHeader.value(); required = requestHeader.required(); defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue()); annotationsFound++; } else if (RequestBody.class.isInstance(paramAnn)) { requestBodyFound = true; annotationsFound++; } else if (CookieValue.class.isInstance(paramAnn)) { CookieValue cookieValue = (CookieValue) paramAnn; cookieName = cookieValue.value(); required = cookieValue.required(); defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue()); annotationsFound++; } else if (PathVariable.class.isInstance(paramAnn)) { PathVariable pathVar = (PathVariable) paramAnn; pathVarName = pathVar.value(); annotationsFound++; } else if (ModelAttribute.class.isInstance(paramAnn)) { ModelAttribute attr = (ModelAttribute) paramAnn; attrName = attr.value(); annotationsFound++; } else if (Value.class.isInstance(paramAnn)) { defaultValue = ((Value) paramAnn).value(); } else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) { validate = true; Object value = AnnotationUtils.getValue(paramAnn); validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value}); } } if (annotationsFound > 1) { throw new IllegalStateException("Handler parameter annotations are exclusive choices - " + "do not specify more than one such annotation on the same parameter: " + handlerMethod); } if (annotationsFound == 0) { Object argValue = resolveCommonArgument(methodParam, webRequest); if (argValue != WebArgumentResolver.UNRESOLVED) { args[i] = argValue; } else if (defaultValue != null) { args[i] = resolveDefaultValue(defaultValue); } else { Class<?> paramType = methodParam.getParameterType(); if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) { if (!paramType.isAssignableFrom(implicitModel.getClass())) { throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " + "Model or Map but is not assignable from the actual model. You may need to switch " + "newer MVC infrastructure classes to use this argument."); } args[i] = implicitModel; } else if (SessionStatus.class.isAssignableFrom(paramType)) { args[i] = this.sessionStatus; } else if (HttpEntity.class.isAssignableFrom(paramType)) { args[i] = resolveHttpEntityRequest(methodParam, webRequest); } else if (Errors.class.isAssignableFrom(paramType)) { throw new IllegalStateException("Errors/BindingResult argument declared " + "without preceding model attribute. Check your handler method signature!"); } else if (BeanUtils.isSimpleProperty(paramType)) { paramName = ""; } else { attrName = ""; } } } if (paramName != null) { args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler); } else if (headerName != null) { args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler); } else if (requestBodyFound) { args[i] = resolveRequestBody(methodParam, webRequest, handler); } else if (cookieName != null) { args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler); } else if (pathVarName != null) { args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); } else if (attrName != null) { WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler); boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1])); if (binder.getTarget() != null) { doBind(binder, webRequest, validate, validationHints, !assignBindingResult); } args[i] = binder.getTarget(); if (assignBindingResult) { args[i + 1] = binder.getBindingResult(); i++; } /** *這裏將request域中的請求參數覆蓋到@SessionAttribute和@ModelAttribute賦值的implicitModel中。 *這樣獲得的結果就是@SessionAttribute和ModelAttribute先將全部字段初始化,而後和request中的請求值構成並集, *這樣得出的結果就是request中沒有的字段保留,若是key同樣的,就覆蓋新值。 *也就獲得咱們想要的結果。最終在目標方法中獲得的就是已被改寫的參數。 **/ implicitModel.putAll(binder.getBindingResult().getModel()); } } return args; }

整個的運行流程

  1. 發起request請求—->
  2. 各類filter—>
  3. org.springframework.web.servlet.DispatcherServlet#doDispatch—–>
  4. org.springframework.web.servlet.HandlerAdapter#handle(具體執行適配器爲:org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#handle)—–>
  5. org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#invokeHandlerMethod——>
  6. invokeHandlerMethodd的for (String attrName : this.methodResolver.getActualSessionAttributeNames())(先看看SessionAttribute修飾的Handler有沒有值,有就放入org.springframework.ui.ExtendedModelMap implicitModel 中)——>
  7. invokeHandlerMethodd的for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) (再看@ModelAttribute修飾的全部方法,若是有就循環每一個ModelAttribute修飾的方法,並將每一個方法的返回值放入org.springframework.ui.ExtendedModelMap implicitModel )——>
  8. org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments(將@SessionAttribute和@ModelAttribute初始化的對象和本身的請求參數並集(相關代碼:Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); )做爲本身請求的目標方法的入參,注意,若是沒有使用ModelAttribute註解,則目標方法的入參key爲POJO類名的首字母小寫。)—–>(反射執行本身請求的目標方法。handlerMethodToInvoke.invoke(handler, args);)—->
  9. 最後方法獲得已被處理過的入參。執行業務操做。
    SpringMVC肯定POJO入參的過程

這裏須要注意的是:

若是implicitModel不存在key對應的對象(也就是說ModelAttribute中也沒有爲key賦值),則會檢查當前Handler是否標註了SessionAttribute註解 且value屬性值包含了這個key,則會從HttpSession中尋找這個key對應的value值,若是有則綁定到目標方法的入參中,若是返回null,將拋出異常:org.springframework.web.HttpSessionRequiredException: Session attribute ‘user’ required - not found in session
相關源代碼

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam, ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception { // Bind request parameter onto object... String name = attrName; if ("".equals(name)) { name = Conventions.getVariableNameForParameter(methodParam); } Class<?> paramType = methodParam.getParameterType(); Object bindObject; if (implicitModel.containsKey(name)) { bindObject = implicitModel.get(name); } else if (this.methodResolver.isSessionAttribute(name, paramType)) { //若是有SessionAttribute註解,則查找HttpSession中的值 bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name); //若是獲取的值爲NULL,就拋異常HttpSessionRequiredException異常 if (bindObject == null) { raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session"); } } else { bindObject = BeanUtils.instantiateClass(paramType); } WebDataBinder binder = createBinder(webRequest, bindObject, name); initBinder(handler, name, binder, webRequest); return binder; }

練習13 視圖和視圖解析器

請求處理方法完成後,最終會返回一個ModelAndView對象,對於返回String,View或ModelMap等類型的處理方法,SpringMVC也會在內部將它裝配成一個ModelAndView對象,它包含了邏輯名和模型對象的視圖。
SpringMVC藉助視圖解析器(ViewResolver)獲得最終的視圖對象(View),最終的視圖能夠是JSP,也多是Excel、jfreechart等各類形式的視圖。
對於最終採起何種視圖對象對模型數據進行渲染,處理器並不關心,處理器的工做重點聚焦在生產模型數據的工做上,從而實現了MVC的充分解耦。

視圖

視圖的做用是渲染模型數據,將模型裏的數據以某種形式呈現給客戶。爲了實現視圖模型和具體實現技術的解耦,spring在org.springframework.web.servlet包中定義了一個高度抽象的接口View
這裏寫圖片描述
視圖對象由視圖解析器負責實例化,因爲視圖是無狀態的,因此他們不會有線程安全問題。

視圖解析器

SpringMVC爲邏輯視圖名的解析提供了不一樣的策略,能夠在spring WEB 上下文中配置一種或多種解析策略,並指定他們的前後順序。每一種映射策略對應一個具體的視圖解析器實現類。
視圖解析器的做用比較單一:將邏輯視圖解析爲一個具體的視圖對象。
全部的視圖解析器都必須實現org.springframework.web.servlet.ViewResolver接口
這裏寫圖片描述

InternalResourceViewResolver

JSP是最多見是視圖技術,可使用InternalResourceViewResolver做爲視圖解析器:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
<!--配置視圖解析器--> <bean id="resolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--返回前綴--> <property name="prefix" value="/"></property> <!--後綴--> <property name="suffix" value=".jsp"></property> </bean>

若項目中用了JSTL,則SpringMVC會自動把InternalResourceViewResolver轉換爲JstlView
若使用了JSTL的fmt標籤,則須要在SpringMVC的配置文件中配置國際化資源文件

  
  
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> </bean>

若但願直接響應SpringMVC渲染的頁面,可使用
<mvc:view-controller path="user/testMap" view-name="success" />

源碼分析

經過InternalResourceViewResolver瞭解視圖解析流程,
InternalResourceViewResolver是咱們經常使用來渲染JSP的解析器,它繼承了UrlBasedViewResolver 實現了ViewResolver的resolveViewName方法,獲取最終實現視圖渲染的解析器,從而最終獲得咱們想要的視圖。
上次咱們瞭解處理器運行流程的時候跟蹤的源碼是handle方法
此次咱們是須要跟蹤處理完成後返回視圖的運行流程
這裏意思是無論執行結果是什麼,都返回ModelAndView
頂層入口
org.springframework.web.servlet.DispatcherServlet#doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
這裏返回的ModelAndView以下:
這裏寫圖片描述
//這裏就是處理結果後續操做,其中包括視圖渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//進入方法後
org.springframework.web.servlet.DispatcherServlet#processDispatchResult

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
//若是有錯誤,就進行錯誤視圖渲染 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); } } //若是沒有錯誤就進入這一步 if (mv != null && !mv.wasCleared()) { //渲染視圖 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } }

進入render
org.springframework.web.servlet.DispatcherServlet#render

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
View view; if (mv.isReference()) { // We need to resolve the view name. //這裏說的很清楚了,須要獲得視圖名字, //經過後面的源碼咱們能夠知道,這裏返回的是InternalResourceViewResolver的子類JstlView view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } }

進入resolveViewName
org.springframework.web.servlet.DispatcherServlet#resolveViewName

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
//this.viewResolvers這裏是全部的視圖解析器,這裏包括了InternalResourceViewResolver for (ViewResolver viewResolver : this.viewResolvers) { //經過controller要返回的結果,找到匹配的視圖,這裏返回的是JstlView //由於InternalResourceViewResolver繼承了UrlBasedViewResolver,因此能夠經過UrlBasedViewResolver的loadView方法得到view(view的實現類AbstractUrlBasedView)的URL及其餘屬性 View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { //這裏返回的是InternalResourceViewResolver的子類JstlView return view; } }

得到view的正確視圖後執行渲染方法
org.springframework.web.servlet.DispatcherServlet#render

  
  
  
  
  • 1
  • 2
  • 1
  • 2
//真正渲染,實際上是view實現類的render方法,這裏執行的是JstlView的父類InternalResourceView的父類AbstractUrlBasedView的父類AbstractView的render方法 `view.render(mv.getModelInternal(), request, response);`

進入
org.springframework.web.servlet.view.AbstractView#render,必定要明白,這個方法是經過org.springframework.web.servlet.DispatcherServlet#render調用的,具體實現就是InternalResourceViewResolver的子類JstlView

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes); } Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); //這裏執行對應的實現類渲染 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }

咱們用的是InternalResourceView,由於DispatcherServlet中得到的view是是InternalResourceViewResolver的子類JstlView,且執行了render()方法,進而能夠執行父類的renderMergedOutputModel方法。

org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ... ... //關鍵看這裏RequestDispatcher RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); ... ... // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); ... ... rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. ... ... //這裏進行請求轉發 rd.forward(request, response); } }

從以上源碼能夠大概瞭解到咱們配置的InternalResouceViewResolver的視圖渲染過程,也能夠觸類旁通明白其餘視圖解析器的渲染過程。

經常使用的視圖實現類

大類 視圖類型 說明
URL資源視圖 InternalResouceView 將JSP或其餘資源封裝成一個視圖,是InternalResouceViewResolver默認的視圖實現類
URL資源視圖 JstlView 若是JSP中使用了JSTL國際化標籤的功能,則須要使用該視圖實現類
文檔視圖 AbstractExcelView Excel文檔視圖的抽象類,該視圖基於POI構造Excel文檔
文檔視圖 AbstractPdfView Excel文檔視圖的抽象類,該視圖基於iText構造PDF文檔
報表視圖 ConfigurableJasperReportsView 使用JasperReports報表技術的視圖
報表視圖 JasperReportsCsvView 同上
報表視圖 JasperReportsHtmlView 同上
報表視圖 JasperReportsPdfView 同上
報表視圖 JasperReportsXlsView 同上
報表視圖 JasperReportsMultiFormatView 同上
JSON視圖 MappingJackson2JsonView 將模型數據經過Jackson開業框架的ObjectMapper以JSON方式輸出

經常使用的視圖解析器實現類

大類 視圖類型 說明
解析爲Bean的名字 BeanNameViewResolver 將邏輯視圖名解析爲一個Bean,Bean的id等於邏輯視圖名
解析爲URL文件 InternalResourceViewResolver 將視圖名解析爲一個URL文件,通常使用該解析器將視圖名映射爲一個保存在WEB-INF下的程序文件,如.jsp
解析爲URL文件 JasperReportsViewResolver JasperReports是一個基於Java的開源報表工具,該解析器將視圖名解析爲報表文件對應的URL
模板文件視圖 FreeMarkerViewResolver 解析爲基於FreeMarker模板技術的模板文件
模板文件視圖 VelocityViewResolver 解析爲基於Velocity模板技術的模板文件
模板文件視圖 VelocityLayoutViewResolver 同上

練習14 springMVC+JSTL 實現國際化

步驟1:須要在項目中導入jstl依賴庫,並在須要國際化的頁面引入jstl的fmt標籤
步驟2:配置資源文件(在src/main/resource下)
資源文件基本都是KV結構因此這三個文件內容的key都必須同樣,value就換爲對應的值就能夠了
i18n.properties

  
  
  
  
  • 1
  • 2
  • 1
  • 2
i18n.name=name i18n.pwd=password

i18n_en_US.properties

  
  
  
  
  • 1
  • 2
  • 1
  • 2
i18n.name=name i18n.pwd=password

i18n_zh_CN.properties

  
  
  
  
  • 1
  • 2
  • 1
  • 2
i18n.name=用戶名 i18n.pwd=密碼

步驟3:springMVC配置文件

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
<!--配置國際化資源文件,注意basename的value值,須要對應資源文件--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> <property name="defaultEncoding" value="UTF-8"></property> </bean>

步驟4:須要國際化的頁面

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <html> <head> <title>user info</title> </head> <body> <p> <fmt:message key="i18n.name"></fmt:message></p> </body> </html>

這樣,隨着本地語言環境的變化,name會自動切換爲對應的語言

練習15 mvc:view-controller

有適合咱們須要直接訪問某個視圖,不須要通過controller,那麼能夠增長springMVC的配置

  
  
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
<mvc:annotation-driven></mvc:annotation-driven> <!--path映射訪問地址 view-name爲視圖名稱--> <mvc:view-controller path="/abc" view-name="userInfo"></mvc:view-controller>

這樣訪問http://ip:port/abc就能夠跳轉到userInfo.jsp了,注意mvc:annotation-driven這個配置,這是爲了防止配置view-controller後沒法正常訪問controller而增長的。至於什麼緣由,以後補充。

練習16 自定義視圖之BeanNameViewResolver

咱們有的時候須要經過自定義的視圖解決業務的須要,如公司內部的模板等等…
這裏咱們經過BeanNameViewResolver這個視圖解析器來看看自定義視圖的實現方式。
首先咱們須要一個View接口的實現類MyView1.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
/** * 由於BeanNameViewResolver是直接根據視圖名稱來獲取的, * 因此須要用@Component註解加入到spring容器中 */ @Component public class MyView1 implements View { @Override public String getContentType() { //返回內容類型 return "text/html"; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { PrintWriter pw = response.getWriter(); pw.print("this is myView page , Time = "+System.currentTimeMillis()); pw.close(); } }

而後咱們須要爲springMVC增配

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<!--配置beanNameViewResolver視圖解析器,使用視圖的名字來解析視圖,order爲解析順序,越小優先級越高--> <!--因此自定義的視圖須要保證比InternalResourceViewResolver優先級要高--> <!--而InternalResourceViewResolver的order屬性爲Integer的最大值,因此這裏order能夠爲任何值--> <bean id="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="10"></property> </bean>

順便看下源碼,看到BeanNameViewResolver是經過context.getBean返回我們定義的視圖,因此這就是爲何要把我們的View實現類放到容器中的緣由。也就是爲何咱們在controller中返回視圖名稱就能夠實現自定義視圖了。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@Override public View resolveViewName(String viewName, Locale locale) throws BeansException { .... return context.getBean(viewName, View.class); }

最後再controller中寫測試方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@RequestMapping("/testView") public String testView(){ System.out.println("testView"); //這個名稱就是咱們的自定義視圖類 return "myView1"; }

測試結果:
頁面會打印出

  
  
  
  
  • 1
  • 1
this is myView page , Time = 1452041832760

其餘的視圖解析器能夠查看練習13下的《經常使用的視圖解析器實現類》

練習17 重定向

通常狀況下,控制器方法返回字符串類型的值會被當作邏輯視圖名處理。
若是返回的字符串中含有forward:或redirect:前綴時,SpringMVC會對他們進行特殊處理,將forward:和redirect:當成指示符處理,其後的字符串當成URL來處理。
redirect:success.jsp 會完成一個到success.jsp的重定向
forward:success.jsp 會完成一個到success.jsp的轉發

源碼分析

爲了看怎麼處理返回結果(即:返回哪一個視圖VIEW。也就是View接口的哪一個實現類),定位到doDispatch這個方法,看過上面源碼分析的朋友,相信已經很熟悉了
頂層方法doDispatch
org.springframework.web.servlet.DispatcherServlet#doDispatch
進入processDispatchResult
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
進入render
org.springframework.web.servlet.DispatcherServlet#render
進入resolveViewName
org.springframework.web.servlet.DispatcherServlet#resolveViewName

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { /**這裏就獲得咱們所配置的View,而且注意List<ViewResolver> viewResolvers這個List是排序的, *會按照配置的視圖解析器的order屬性進行存取 *練習16中,咱們定義了兩個視圖解析器,一個是InternalResourceViewResolver *還有一個是BeanNameViewResolver, *這裏會首先用BeanNameViewResolver的resolveViewName方法嘗試返回View對象, *可是咱們是經過return "redirect:/index.jsp"返回的, *因此BeanNameViewResolver經過他的context.getBean(...)方法沒法根據viewName獲取到對應的View *因此進入下一次循環。獲得的是InternalResourceViewResolver, *而InternalResourceViewResolver用的是父類UrlBasedViewResolver的父類AbstractCachingViewResolver的 *resolveViewName方法; **/ View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }

進入
org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
@Override public View resolveViewName(String viewName, Locale locale) throws Exception { if (!isCache()) { //UrlBasedViewResolver重寫了createView方法,因此這裏調用的是UrlBasedViewResolver的createView return createView(viewName, locale); } else { Object cacheKey = getCacheKey(viewName, locale); View view = this.viewAccessCache.get(cacheKey); //若是緩存沒有,會把我們返回的視圖解析器進行緩存,下次就直接返回。 if (view == null) { synchronized (this.viewCreationCache) { view = this.viewCreationCache.get(cacheKey); if (view == null) { // Ask the subclass to create the View object. //重點看createView方法 //UrlBasedViewResolver重寫了createView方法,因此這裏調用的是UrlBasedViewResolver的createView view = createView(viewName, locale); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); if (logger.isTraceEnabled()) { logger.trace("Cached view [" + cacheKey + "]"); } } } } } return (view != UNRESOLVED_VIEW ? view : null); } }

這裏須要明白抽象類之間的相互調用,以避免混淆的具體實現方法。
最後看org.springframework.web.servlet.view.UrlBasedViewResolver#createView

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
@Override protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } /** * 這裏就一目瞭然了。redirect返回的是RedirectView * forward返回的是InternalResourceView **/ // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); return applyLifecycleMethods(viewName, view); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); }

練習18 springMVC表單標籤

經過使用springMVC的表單標籤能夠實現將模型數據中的屬性與HTML表單元素相綁定,以實現表單數據更便捷編輯與表單值回顯
1、Form標籤
通常狀況下經過GET請求獲取表單頁面,而經過POST請求提交表單頁面,所以獲取表單頁面和提交表單頁面的URL是相同的。只要知足最佳條件的契約,<form:form>標籤就無需經過action屬性指定表單起腳的URL。
能夠經過modelAttribute屬性指定綁定模型屬性,若沒有指定該屬性,則默認從request域中讀取command的表單bean。如該屬性值也不存在,則報以下錯誤:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
2、表單標籤
springMVC提供了多個表單組件標籤,如<form:input />、<form:select />等,用以綁定表單字段的屬性值,它們的共有屬性以下:
path:表單字段,對應HTML的name屬性,支持級聯屬性
htmlEscape:是否對錶單值的特殊字符進行轉換,默認爲true
cssClass:表單組件的css樣式名
cssErrorClass:表單組件的數據存在錯誤時,採起的css樣式
<form:input /><form:password /><form:hidden /><form:textarea />:對應HTML表單的text,password,hidden,textarea標籤
<form:radiobutton />:單選框標籤,當表單bean的屬性值和value相同時,單選框被選中。
<form:radiobuttons />:單選框組標籤,用於構造多個單選框
- items:能夠是一個list,string[] 或map
- itemValue:是定radio的value值,能夠是集合bean中的一個屬性值
- itemLabel:指定radio的label名稱
- delimiter:多個單選框能夠指定分隔符
<form:checkbox />:複選框組件,用於構造單個複選框
<form:checkboxs />:用於構造多個複選框,使用方式同<form:radiobuttons />標籤
<form:select />:用於構造下拉組件,使用方式同<form:radiobuttons />標籤
<form:option />:下拉框選項組件標籤,使用方式同<form:radiobuttons />標籤
<form:errors />:顯示錶單組件或數據校驗所對應的錯誤
- <form:errors path="*"/>:顯示錶單全部的錯誤
- <form:errors path="user*"/>:顯示以user爲前綴的屬性對應的錯誤
- <form:errors path="username"/>:顯示特定表單對象屬性的錯誤
3、表單標籤練習
經過一個小例子練習上面幾個標籤

STEP1 :準備數據

初始化數據類,由於沒有DAO,因此我放到static變量中。無論經過說明辦法,初始化數據就OK。我是經過spring的初始化配置方法進行初始化的。

  
  
  
  
  • 1
  • 2
  • 1
  • 2
<bean id="dataInit" class="com.iboray.smt.commons.DataInit" scope="singleton" lazy-init="false" init-method="init"></bean>
  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
public class DataInit { private static List<User> users = null; private static List<Detp> detps = null; private void init(){ System.out.println("數據初始化... ..."); users = new ArrayList<>(); users.add(new User(1,"zs","112233",new Detp(1,"dept1"))); users.add(new User(2,"ls","22233",new Detp(1,"dept1"))); users.add(new User(3,"wz","41223",new Detp(2,"dept2"))); users.add(new User(4,"zl","535454",new Detp(3,"dept3"))); users.add(new User(5,"mq","565575",new Detp(3,"dept3"))); detps = new ArrayList<>(); detps.add(new Detp(1,"dept1")); detps.add(new Detp(2,"dept2")); detps.add(new Detp(3,"dept3")); detps.add(new Detp(4,"dept4")); } public static List<User> getUsers() { return users; } public static List<Detp> getDetps() { return detps; } }

STEP2 :增長controller對應的方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
@Controller @RequestMapping(value = "/user") public class UserController { //保存,並重定向 @RequestMapping(value = "/saveUser",method = RequestMethod.POST) public String saveUser(User user){ DataInit.getUsers().add(user); List l = DataInit.getUsers(); return "redirect:/user/userInput"; } /** *這裏是偷懶。須要說明一下,爲了少寫一個頁面 *因此添加和列表都放在同一個頁面中。 * **/ @RequestMapping(value = "/userInput",method=RequestMethod.GET) public String userInput(Map<String,Object> map){ //對應form:form標籤的modelAttribute屬性,不然會報上面提到的錯誤 map.put("user",new User()); //對應form:select 中的items屬性 map.put("depts", DataInit.getDetps()); //查詢列表所需 map.put("users",DataInit.getUsers()); return "springForm"; } }

STEP3 :增長展示頁面springForm.jsp

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <form:form action="/user/saveUser" method="post" modelAttribute="user"> <form:input path="id" ></form:input> <form:input path="name" ></form:input> <form:input path="pwd" ></form:input> <form:select path="dept.id" items="${depts }" itemLabel="deptName" itemValue="id" ></form:select> <input type="submit" value="submit"> </form:form> <hr> <table border="1"> <c:forEach items="${users}" var="user"> <tr> <td width="20">${user.id}</td> <td width="150">${user.name}</td> <td width="150">${user.pwd}</td> <td width="20">${user.dept.id}</td> <td width="40">${user.dept.deptName}</td> </tr> </c:forEach> </table> </body> </html>

STEP4 :請求地址

請求:http://ip:port/user/userInput
返回:submit後結果
1到5爲我們初始化數據
這裏寫圖片描述

練習19 處理靜態資源

REST風格的資源URL不但願帶有.html或.do等後綴,若將dispatcherServlet的請求映射爲/,則springMVC將捕獲web應用的全部請求,包括靜態資源的請求,springMVC會將他當成一個普通的請求來處理,因找不到對應處理器而報錯
咱們能夠在springMVC配置中增長<mvc:default-servlet-handler />的方式解決靜態資源的問題。
<mvc:default-servlet-handler />將在springMVC上下文中定義一個defaultservlethttprequesthandler,他會對進入dispatcherServlet的請求進行篩查,如發現沒有通過映射的請求,就講請求交由web應用服務器默認的servlet處理,若是不是靜態資源且有映射的才交由dispatcherservlet進行處理
通常web應用服務器默認的servlet的名稱都是default,因此不用顯示配置<mvc:default-servlet-handler />的default-servlet-name=」「屬性,若不是默認的名稱,則須要配置。
特別提醒,若是配置了<mvc:default-servlet-handler />,須要配置<mvc:annotation-driven >,緣由後面再單說。

練習20 自定義類型轉換器

有時候要寫自定義類型轉換器,那就得先了解一下數據綁定流程。

數據綁定流程

  1. springMVC主框架將servletRequest對象及目標方法的入參實例傳遞給webDataBinderFactory實例(可再源碼分析查看內部結構),以建立DataBinder實例對象。
  2. DataBinder調用裝配在springMVC上下文中的conversionService組件進行數據類型轉換,數據格式化工做,將servlet請求信息填充到入參對象中。
  3. 調用validator組件對已綁定了請求消息的入參對象進行數據合法性校驗,並最終生成數據綁定結果BindingData對象。
  4. springMVC抽取BindingResult中的入參對象和校驗錯誤對象,將它們賦給處理方法的響應入參。
    數據綁定
    springMVC經過反射機制對目標處理方法進行解析,將請求消息綁定處處理方法的入參中,數據綁定的核心部件是DataBinder,運行機制以下:
    這裏寫圖片描述

數據轉換

springMVC上下文中內建了不少轉換器,可完成大多數java類型的轉換工做。

自定義類型轉換器

  1. ConversionService是Spring類型轉換體系的核心接口,能夠利用ConversionServiceFactoryBean在Spring IOC容器中定義一個ConversionService。Spring將自動識別出IOC容器中的ConversionService,並在Bean屬性配置及SpringMVC處理方法入參綁定等場合使用它進行數據轉換。
  2. 可經過ConversionServiceFactoryBean的converters屬性註冊自定義的類型轉換器。

spring支持的類型轉換器

spring定義了三種類型的轉換器接口,實現任意一個接口均可以做爲自定義類型轉換器註冊到ConversionServiceFactoryBean中
1. Converter<S,T>將S類型對象轉換爲T類型對象
2. ConverterFactory 將相同系列多個「同質」Converter封裝在一塊兒,若是但願將一種類型對象轉換爲另外一種類型及子類的對象(例如將String轉換爲Number及Number的子類[Interger,Double,Long]等)可以使用該轉換器工廠類
3. GenericConverter會根據源類對象及目標類對象所在的宿主類中的上下文信息進行類型轉換

寫一個自定義類型轉換器UserConverterService

STEP 1:

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
@Component public class UserConverterService implements Converter<String,User> { @Override public User convert(String source) { //一下子在這裏打斷點,查看ConversionService是否加入了咱們的轉換器 if(source != null && !"".equals(source.trim())){ String[] s = source.split(";"); if (s.length == 3){ User u = new User(Integer.parseInt(s[0]),s[1],s[2]); System.out.println("source:"+source); System.out.println("result:"+u); return u; } } return null; } }

STEP 2 :SpringMVC配製文件

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<mvc:annotation-driven conversion-service="conversionService" ></mvc:annotation-driven> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="userConverterService"></ref> </set> </property> </bean>

STEP 3: Controller方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
/**看入參@RequestParam("userStr") User user,實際上是咱們請求的字符串,但咱們是用User接參的。 *經過http://host/user/saveUserByConverter?userStr=13;qweqwe;xxxooooo請求地址, *找到咱們對應的controller,在此期間,通過了上面說的數據綁定流程, *判斷出咱們須要的轉換器是String 轉 User對象的。 *那正好是咱們寫的自定義轉換器,因此就能夠正常轉換了。 */ @RequestMapping(value = "/saveUserByConverter",method = RequestMethod.GET) public String saveUserByConverter(@RequestParam("userStr") User user){ DataInit.getUsers().add(user); List l = DataInit.getUsers(); return "redirect:/user/userInput"; }

源碼分析

在自定義的轉換器中打斷點,找到以下方法
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
if (binderFactory != null) { //查看WebDataBinder對象 WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); arg = binder.convertIfNecessary(arg, paramType, parameter); }

WebDataBinder對象:
這裏寫圖片描述

測試

  1. 請求:http://host/user/saveUserByConverter?userStr=13;qweqwe;xxxooooo
  2. 在自定義的converter中打斷點,查看變量
    這裏寫圖片描述
    進一步查看
    這裏寫圖片描述
    ……
    這裏寫圖片描述
    能夠看出已經有咱們本身的converter了
  3. console輸出:
  
  
  
  
  • 1
  • 2
  • 1
  • 2
source:13;qweqwe;xxxooooo result:User{id=13, name='qweqwe', pwd='xxxooooo', dept=null}

練習21 mvc:annotation-driven

<mvc:annotation-driven>會自動註冊RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver三個Bean。
還提供如下支持。
1. 支持使用ConversionService實例對錶單數據進行類型轉換
2. 支持使用@NumberFormatAnnotation、@DateTimeFormat註解完成數據類型格式化。
3. 支持使用@Valid註解對JavaBean實例進行JSR 303驗證
4. 支持使用@RequestBody和@ResponseBody註解

對比配置annotation-driven先後的差別

都在org.springframework.web.servlet.DispatcherServlet#doDispatchmv = ha.handle(processedRequest, response, mappedHandler.getHandler());打斷點重點查看handlerAdapters
1. 既沒有配置<mvc:default-servlet-handler />也沒有配置<mvc:annotation-driven>
這裏寫圖片描述
請求正常,可是查看源碼AnnotationMethodHandlerAdapter已經有刪除線了Spring已經不建議使用了,具體緣由這裏不闡述了。
2. 配置<mvc:default-servlet-handler />沒有配置<mvc:annotation-driven>
這裏寫圖片描述
請求出錯 HTTP Status 404 - /user/saveUser
3. 既配置<mvc:default-servlet-handler />也配置<mvc:annotation-driven>
這裏寫圖片描述
請求正常
這樣經過對比能夠看出<mvc:annotation-driven>的做用,因此在使用了springMVC框架的項目中,建議都增長該配置。

練習22 initBinder

由InitBinder標識的方法能夠對webDataBinder對象進行初始化。webDataBinder是DataBinder的子類,用於完成由表單字段到javaBean屬性的綁定。
InitBinder方法不能有返回值,必須聲明void。
InitBinder方法的參數一般是webDataBinder。
數據綁定流程參考練習20自定義類型轉換器中的數據綁定流程
練習代碼
controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
@InitBinder public void testInitBinder(WebDataBinder binder){ //不綁定表單name爲name的值。 binder.setDisallowedFields("name"); }

練習23 數據格式化

若是咱們配置了annotation-driver而且使用了格式化註解 例如:NumberFormat或DateTimeFormat,那麼springMVC在處理數據綁定的過程當中用的轉換器就是FormattingConversionServiceFactoryBean。

FormattingConversionServiceFactoryBean內部已註冊了:
1. NumberFormatAnnotationFormaterFactory:支持對數字類型的屬性用@NumberFormat註解
2. JodaDateTimeFormatAnnotationFormatterFactory:支持對日期類型屬性使用@DateTimeFormat註解
裝配了FormattingConversionServiceFactoryBean後就能夠在springMVC入參綁定及模型數據輸出時使用註解驅動了。mvc:annotation-driven默認建立的ConversionService實例即爲FormattingConversionServiceFactoryBean。

日期格式化

@DateTimeFormat註解可對java.util.Date、java.util.Calendar、java.long.Long時間類型進行標註:
1. pattern屬性:類型爲字符串,指定解析和格式化字段數據的模式,如:yyyy-MM-dd
2. iOS屬性:類型爲DateTimeFormat.ISO,指定解析和格式化字段數據的ISO模式,包括四種,ISO.NONE(不使用,默認),ISO.DATE,ISO.TIME,ISO.DATE_TIME
3. style屬性,字符串類型,經過樣式指定日期時間的格式,由兩位字符組成,第一位表示日期的格式,第二爲表示時間的格式,S:短日期/時間 格式,M:中日期/時間 格式,L:長日期/時間 格式,F:完整日期/時間 格式,- :忽略日期或時間格式。

數值格式化

@NumberFormat可對數字類型的屬性進行標註,它擁有兩個互斥的屬性:
1. style:類型爲NumberFormat.Style。用於指定樣式類型,包括三種:Style.NUMBER(正常數字類型),Style.CURRENCY(貨幣類型),Style.PERCENT(百分比)
2. pattern:類型爲String,自定義樣式,如pattern=」#,###,###.#」。

源碼分析

斷點進入
org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
查看WebDataBinder binder
這裏會有三種狀況出現,你們須要注意一下
接參的bean:User.java

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
public class User { private int id; private String name; private String pwd; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; @NumberFormat(pattern = "#,###,###.#") private BigDecimal pay; getter setter ... ... }

第一種,配置了annotation-driven且自定義類型轉換器是經過ConversionServiceFactoryBean加入的。

這種狀況下即使是加入了@DateTimeFormart @NumberFormat都沒法正確格式化,會出現404錯誤。
springMVC配置以下

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
<mvc:annotation-driven conversion-service="conversionService" ></mvc:annotation-driven> <!--重點在這裏,咱們須要格式化參數,這裏配置的倒是ConversionServiceFactoryBean--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="userConverterService"></ref> </set> </property> </bean>

binder相關部分截圖
這裏寫圖片描述

第二種,只配置annotation-driven

結果是能夠正確格式化參數,但沒法使用自定義類型轉換器。
springMVC配置以下

  
  
  
  
  • 1
  • 1
<mvc:annotation-driven ></mvc:annotation-driven>

binder相關截圖,能夠看出有兩個格式化Parser,可是converters有119個,裏面我看了。沒有咱們本身的String –> User的轉換器,這個轉換器功能見數據綁定流程參考練習20自定義類型轉換器中的數據綁定流程
這裏寫圖片描述

第三種,配置了annotation-driven且自定義類型轉換器是經過FormattingConversionServiceFactoryBean加入的。

springMVC配置以下

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<mvc:annotation-driven conversion-service="conversionService" ></mvc:annotation-driven> <!--這裏能夠看出咱們用的是FormattingConversionServiceFactoryBean的轉換器--> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="userConverterService"></ref> </set> </property> </bean>

binder的相關截圖以下,能夠看出有兩個格式化Parser,而且converters有120個,裏面我看了,包含咱們本身的類型轉換器。
這裏寫圖片描述

結論

經過測驗,咱們知道,若是咱們須要格式化且有本身的類型轉換器,那咱們須要注意在增長自定義類型轉換器時,須要經過FormattingConversionServiceFactoryBean來加入。這樣才能正常轉換,而且正常格式化。

練習24 數據校驗

原理

Spring4.0擁有本身獨立的數據校驗框架,同時支持JSR303標準的校驗框架。
Spring在進行數據綁定時,可同時調用數據校驗框架完成數據校驗工做,在springMVC中可直接經過註解驅動的方式進行數據校驗。
Spring的LocalValidatorFactoryBean既實現了spring的validator接口,也實現了JSR303的validator接口,只要在spring容器中定義一個LocalValidatorFactoryBean便可將其注入到須要校驗的bean中。
Spring自己並無提供JSR303的實現,因此必須將JSR303的實現者的jar包放到類路徑下。
<mvc:annotation-driven>會默認裝配好一個LocalValidatorFactoryBean,經過在處理方法的入參上標註@Valid註解,便可讓springMVC在完成數據綁定後執行數據校驗工做。
在已經標註了JSR303註解的表單/命令對象前標註一個@Valid,springMVC框架在將請求參數綁定到該入參對象後,就會調用驗證框架根據註解聲明的校驗規則實施校驗。
springMVC是經過對處理方法簽名的規約來保存校驗結果的:前一個表單/命令對象(springForm表單中Form標籤的modelAttribute屬性就是命令對象)的校驗結果保存到隨後的入參中,這個保存校驗結果的入參必須是BindingResult或Errors類型。這兩個類都位於org.springframework.validation包中。
須要校驗的Bean對象和其綁定結果對象或錯誤對象是成對出現的,它們之間不容許聲明其餘入參
Errors接口提供了獲取錯誤信息的方法,如getErrorCount(),result.getFieldErrors()等。
BindingResult擴展了Errors接口

在頁面上顯示錯誤

springMVC除了會將表單/命令對象的校驗結果保存到對象的BindingResult或Errors對象中,還會將全部的校驗結果保存到「隱含模型」中。
即便處理方法的簽名中沒有對錶單/命令對象的結果入參,校驗結果也會保存。
隱含對象中的全部數據最終會經過HttpServletRequest的屬性列表暴露給JSP視圖對象,所以在JSP中能夠獲取錯誤信息
在JSP頁面上可經過<form:errors path="name"></form:errors> 顯示對應屬性的錯誤

國際化

每一個屬性在數據綁定或數據校驗發生錯誤時,都會生成一個FieldError對象。
當一個屬性校驗失敗後,校驗框架會爲該屬性生成4個教習代碼,這些代碼以校驗註解類名爲前綴,結合modelAttribute,屬性名及屬性類型名生成多個對應的消息代碼,例如User類中的pwd屬性標註了一個Pattern註解,當屬性不知足Pattern的規則時,就會產生如下4個消息代碼:
1. Pattern.user.pwd
2. Pattern.pwd
3. Pattern.java.lang.String
4. Pattern
當使用springMVC的Form標籤顯示錯誤消息時,springMVC會查看上下文是否裝配了對應的國際消息。若是沒有,則顯示默認的錯誤消息,如有,就顯示對應的國際化消息。
若數據類型轉換或數據格式化發生錯誤時,或該有的參數不存在時,或調用處理方法發生錯誤時,都會在隱含模型中建立錯誤消息,其錯誤代碼前綴說明以下。
1. required 必要的參數不存在時,如@RequestParam(「param1」) 標註了一個入參,但該參數不存在
2. typeMismatch 在數據綁定時發生類型不匹配。
3. methodInvocation springMVC在調用處理方法時發生了錯誤

測試

STEP1 其次要加入依賴庫
maven就很容易了。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency>

STEP2 添加校驗規則

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
public class User { @NotEmpty private String name; @NotEmpty @Length(max = 5) private String pwd; @Past @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birth; getter setter....... }

STEP3 按以前的練習14配置資源文件。我這裏偷懶就只在中文資源文件i18n_zh_CN.properties裏面配置了。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
i18n.name=用戶名 i18n.pwd=密碼 NotEmpty.user.name=name必填 NotEmpty.user.pwd=pwd必填 Length.user.pwd=長度爲1~5之間 Past.user.birth=非法的出生日期 typeMismatch.user.birth=出生日期格式錯誤

STEP4 隨後是springMVC配置文件的配置,前提是你已經配置了</mvc:annotation-driven>,這個是必須的。具體可參考練習21

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<!--配置國際化資源文件,注意basename的value值,須要對應資源文件--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> <property name="defaultEncoding" value="UTF-8"></property> </bean>

STEP5 而後再改造練習18的controller,固然最好你能夠本身從新寫一個。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
@RequestMapping(value = "/saveUser",method = RequestMethod.POST) public String saveUser(@Valid User user, BindingResult result //User和其綁定結果的對象,必須成對出現,之間不能有其餘入參聲明 ,Map<String,Object> map){ if(result.getErrorCount() > 0){ System.out.println("出錯了,錯誤條數爲:"+result.getErrorCount()); for (FieldError e : result.getFieldErrors()){ System.out.println("ERROR: "+e.getField() + " : "+e.getDefaultMessage()); } /** * 這裏須要注意的是,錯誤會綁定到驗證的入參對象中,而表單若是須要顯示錯誤, * 就須要在<form:form action="xx" method="xx" modelAttribute="user"> * <form:errors path="*" ></form:errors> 顯示全部錯誤 * <form:errors path="name"></form:errors> 顯示對應屬性的錯誤 */ return "springForm"; } DataInit.getUsers().add(user); map.put("depts", DataInit.getDetps()); map.put("users",DataInit.getUsers()); return "redirect:/user/userInput"; }

STEP6 最後是jsp視圖片斷

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
<form:form action="/user/saveUser" method="post" modelAttribute="user"> <form:errors path="*" ></form:errors> <br> id:<form:input path="id" ></form:input><br> name:<form:input path="name" ></form:input> <form:errors path="name"></form:errors><br> pwd:<form:input path="pwd" ></form:input> <form:errors path="pwd"></form:errors><br> birth:<form:input path="birth"></form:input> <form:errors path="birth"></form:errors><br> pay:<form:input path="pay"></form:input><br> deptId:<form:select path="dept.id" items="${depts }" itemLabel="deptName" itemValue="id" ></form:select><br> <input type="submit" value="submit"> </form:form>

練習25 返回Json。分析HttpMessageConverter

在實際開發中,不少狀況下須要給終端返回Json格式的響應,springMVC給咱們提供了很是便捷的返回方式。

返回Json

STEP1 加入Jackson的依賴庫
maven 配置文件

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.7.0</version> </dependency>

STEP2 而後controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
//注意這個註解,這是返回Json必須標註的 @ResponseBody @RequestMapping("/getUserJson") public Object getUserJson(){ List l = DataInit.getUsers(); return l;//直接返回List }

以上兩步就能夠返回Json了,這是爲什麼呢,其實都是一個叫HttpMessageConverter的對象再起做用。

HttpMessageConverter

1、概述

HttpMessageConverter是spring3.0新添加的一個接口,負責將請求信息轉換爲一個對象(類型爲T),將對象(類型爲T)輸出爲響應信息。
HttpMessageConverter<T>接口定義的方法:
1. Boolean canRead(Class<?> clazz,MediaType mediaType):指定轉換器能夠讀取的對象類型,即轉換器是否能夠將請求信息轉換爲clazz類型的對象,同時支持MIME類型(text/html,application/json等)
2. Boolean canWrite(Class<?> clazz,MediaType mediaType):只轉換器是否能夠將類型爲clazz的對象寫入到響應流中,響應流中支持的媒體類型在MediaType中定義。
3. List<MediaType> getSupportMediaType():該轉換器支持的媒體類型。
4. T read(Class<? extends T> clazz,HttpInputMessage inputMessage):將請求信息流轉爲T類型的對象。
5. void write(T t,MediaType contentType,HttpOutputMessage outputMessage):將T類型的對象寫入到響應流中,同時指定相應的媒體類型爲contentType。
使用HttpMessageConverter<T>將請求信息轉換並綁定處處理方法的入參中,或將響應結果轉換爲對應類型的響應信息,spring提供了兩種途徑。
1. 使用@RequestBody/@ResponseBody對處理方法進行標註
2. 使用HttpEntity<T>/ResponseEntity<T>做爲處理方法的入參或返回值。
當控制器使用到以上兩種方法時,Spring首先根據請求頭或響應頭的Accept屬性選擇匹配的HttpMessageConverter,進而根據參數類型或泛型類型的過濾獲得匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter將報錯。
注意:@RequestBody/@ResponseBody不須要成對出現
2、HttpMessageConverter工做原理
這裏寫圖片描述
3、HttpMessageConverter的實現類

序號 實現類 功能說明
1 StringHttpMessageConverter 將請求信息轉爲字符串
2 FormHttpMessageConverter 將表單數據讀取到MultiValueMap中
3 XmlAwareFormHttpMessageConverter 擴展與FormHttpMessageConverter,若是部分表單屬性是XML數據,可用該轉換器進行讀取
4 ResourceHttpMessageConverter 讀寫org.springframework.core.io.Resource對象
5 BufferedImageHttpMessageConverter 讀寫BufferedImage對象
6 ByteArrayHttpMessageConverter 讀寫二進制數據
7 SourceHttpMessageConverter 讀寫java.xml.transform.Source類型的對象
8 MarshallingHttpMessageConverter 經過Spring的org.springframework,xml.Marshaller和Unmarshaller讀寫XML消息
9 Jaxb2RootElementHttpMessageConverter 經過JAXB2讀寫XML消息,將請求消息轉換爲標註的XmlRootElement和XmlType鏈接的類中
10 MappingJacksonHttpMessageConverter 利用Jackson開源包的ObjectMapper讀寫JSON數據
11 RssChannelHttpMessageConverter 讀寫RSS種子消息
12 AtomFeedHttpMessageConverter 和RssChannelHttpMessageConverter可以讀寫RSS種子消息

4、加入Jackson的Jar包,轉換器的先後對比
DispatchServlet默認裝配RequestMappingHandlerAdapter,而RequestMappingHandlerAdapter默認裝配以下HttpMessageConverter:
這裏寫圖片描述
加入Jackson相關依賴包以後RequestMappingHandlerAdapter裝配的HttpMessageConverter增長了轉換Json的轉換器
這裏寫圖片描述
爲了更好的理解HttpMessageConverter,能夠看看這張圖(來自網絡)
這裏寫圖片描述

小練習

Eg:1 經過@RequestBody/@ResponseBody實現將上傳文本文件(並非真正上傳),轉換爲String 並打印
controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@ResponseBody @RequestMapping("/testHttpMessageConverter") public String testHttpMessageConverter(@RequestBody String file ){ System.out.println(file); return "testHttpMessageConverter date : "+ new Date(); }

jsp

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
<form action="/user/testHttpMessageConverter" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" name="submit"> </form>

結果
文件內容
這裏寫圖片描述
打印結果
這裏寫圖片描述
Eg:2 經過ResponseEntity<T>實現下載文件
controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
@RequestMapping("/testResponseEntity") public ResponseEntity<byte[]> testResponseEntity(){ String str = "xttttttsssss >>>>> testResponseEntity"; byte[] body = str.getBytes(); HttpHeaders hh = new HttpHeaders(); hh.add("Content-Disposition","attachment;filename=xxx.txt"); HttpStatus status = HttpStatus.OK; ResponseEntity<byte[]> responseEntity =new ResponseEntity<byte[]>(body,hh,status); return responseEntity; }

jsp

  
  
  
  
  • 1
  • 1
<a href="/user/testResponseEntity" >testResponseEntity</a>

下載的結果是
文件爲:xxx.txt.html
內容爲:
這裏寫圖片描述

練習26 國際化

概述

默認狀況下,springMVC會根據Accept-Language參數判斷客戶端的本地化類型。
當接受請求時,springMVC會在上下文中查找一個本地化解析器(LocalResolver)找到後,使用它獲取請求所對應的本地化類型信息。
springMVC還容許裝配一個動態更改類型本地化類型的攔截器,這樣經過指定一個參數就可用控制單個請求的本地化類型。(由於本地化類型是放到session做用域中)
本地化解析器和本地化攔截器
1. AcceptHeaderLocaleResolver:根據HTTP請求頭的Accept-Language來肯定本地化類型,若是沒有顯示定義本地化解析器,springMVC默認使用該解析器。
2. CookieLocaleResolver:根據指定的Cookie值肯定本地化類型。
3. SessionLocaleResolver:根據Session中特定的本地化參數肯定本地化類型。
4. LocaleChangeInterceptor:從請求中獲取本次請求對應的本地化類型。
加入SessionLocaleResolver先後對比
加入前
這裏寫圖片描述
加入後
這裏寫圖片描述
爲了更好的理解LocaleChangeInterceptor和SessionLocaleResolver,能夠參考以下圖(來自網絡)
這裏寫圖片描述

小練習

經過默認的本地化解析器AcceptHeaderLocaleResolver此次就不測試了,我們直接用SessionLocaleResolver來動態改變本地化類型。
STEP1 準備資源文件。其實這些資源文件我們前面用過的。
在resource目錄下,建三個國際化資源文件
i18n.properties和i18n_en_US.properties內容同樣

  
  
  
  
  • 1
  • 2
  • 1
  • 2
i18n.name=name i18n.pwd=password

i18n_zh_CN.properties內容以下

  
  
  
  
  • 1
  • 2
  • 1
  • 2
i18n.name=用戶名 i18n.pwd=密碼

STEP2 springMVC增配
這裏須要注意的是
1. SessionLocaleResolver的bean的ID必定要是localeResolver,不然會報Cannot change HTTP accept header - use a different locale resolution strategy錯誤
2. 資源文件要配置_en_US或_zh_CN不然找不到對應本地化類型的資源文件。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
<!--配置國際化資源文件,注意basename的value值,須要對應資源文件--> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> <property name="defaultEncoding" value="UTF-8"></property> </bean> <!--配置本地化解析器--> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean> <!--配置本地化攔截器--> <mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean> </mvc:interceptors>

STEP3 controller添加目標方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
@Resource private ResourceBundleMessageSource messageSource; @RequestMapping("/i18n") //Locale這個入參 public String testI18n(Locale locale){ //動態改變本地化類型,而且數據本地化後的值 String str = messageSource.getMessage("i18n.name",null,locale); System.out.println(str); return "index"; }

STEP4 jsp

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
[ <a href="/user/i18n?locale=zh_CN">i18n_Change_zh_CN</a> ]<br> [ <a href="/user/i18n?locale=en_US">i18n_Change_en_US</a> ] <br> <p> <fmt:message key="i18n.name"></fmt:message></p> <p> <fmt:message key="i18n.pwd"></fmt:message></p>

結果:
點擊i18n_Change_zh_CN連接,name和pwd變爲用戶名和密碼,
點擊i18n_Change_en_US連接,name和pwd變爲name和password,它們直接能夠互相切換,而且後臺console窗口能夠輸出對應的值。

源碼分析

STEP1 進入頂層方法
org.springframework.web.servlet.DispatcherServlet#doDispatch

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
//執行攔截器的PreHandle方法,隨後才執行渲染視圖方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }

STEP2 進入
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
這裏寫圖片描述

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { if (getInterceptors() != null) { for (int i = 0; i < getInterceptors().length; i++) { //這裏能夠看到遍歷獲得咱們的LocaleChangeInterceptor HandlerInterceptor interceptor = getInterceptors()[i]; //執行preHandle方法 if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; }

STEP3 進入
org.springframework.web.servlet.i18n.LocaleChangeInterceptor#preHandle

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException { /**能夠看到這裏獲取咱們請求的參數。paramName,這個是已經寫死的。 * public static final String DEFAULT_PARAM_NAME = "locale"; * private String paramName = DEFAULT_PARAM_NAME; **/ String newLocale = request.getParameter(this.paramName); if (newLocale != null) { //根據我們去的請求參數,獲取的結果是SessionLocaleResolver LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); if (localeResolver == null) { throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?"); } //這個方法最終執行的是SessionLocaleResolver的setLocaleContext方法 localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale)); } // Proceed in any case. return true; }

STEP4 進入
org.springframework.web.servlet.i18n.SessionLocaleResolver#setLocaleContext

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
@Override public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) { Locale locale = null; TimeZone timeZone = null; if (localeContext != null) { locale = localeContext.getLocale(); if (localeContext instanceof TimeZoneAwareLocaleContext) { timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); } } //將locale參數放到session中 WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale); WebUtils.setSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME, timeZone); }

STEP5 進入
org.springframework.web.util.WebUtils#setSessionAttribute

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
public static void setSessionAttribute(HttpServletRequest request, String name, Object value) { Assert.notNull(request, "Request must not be null"); if (value != null) { //關鍵在這裏,看看看,放到session中了。 //這就實現了單個會話的本地化動態更改。 request.getSession().setAttribute(name, value); } else { HttpSession session = request.getSession(false); if (session != null) { session.removeAttribute(name); } } }

STEP6 將STEP1 的mv放到org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中

  
  
  
  
  • 1
  • 1
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

STEP7 進入processDispatchResult方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { ... ... // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { //放入了render render(mv, request, response);方法 if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } ... .. }

STEP8 進入render方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = this.localeResolver.resolveLocale(request); //關鍵在這裏,拿到local,寫入響應 response.setLocale(locale); ... ... }

練習27 文件上傳

springMVC爲文件上傳提供了直接的支持,這種支持是經過即插即用的MultipartResolver實現的,spring用Jakarta Commons FileUpload技術實現了一個MultipartResolver實現類CommonsMultipartResolver。
springMVC上下文中默認沒有裝配MultipartResolver,所以默認狀況下不能處理文件上傳工做,如需使用,則須要配置MultipartResolver。
配置MultipartResolver時須要注意兩個點
1. defaultEncoding 必須和用戶JSP的pageEncoding屬性一致,以便正確的解析表單內容。
2. 爲了讓CommonsMultipartResolver正常工做,須要將Jakarta Commons FileUpload及Commons io的包加到項目中。
小練習
STEP1 加入jar包,增長maven配置,就把fileupload和io引入進來

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>

STEP2 springMVC增配

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<!--spring mvc 文件上傳--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="1024000"></property> <property name="defaultEncoding" value="UTF-8"></property> <property name="resolveLazily" value="true"></property> </bean>

STEP3 controller增長目標方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@RequestMapping("/testFileUpload") public String testFileUpload(@RequestParam("file") MultipartFile file) throws IOException { System.out.println("getOriginalFilename :"+file.getOriginalFilename()); System.out.println("getSize :"+file.getSize()); System.out.println("getInputStream :"+file.getInputStream()); return "index"; }

STEP3 jsp增長form表單

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
<form action="/user/testFileUpload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" name="submit"> </form>

結果,後臺正常輸出:

  
  
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
getOriginalFilename :屏幕快照 2016-01-13 上午11.32.04.png getSize :48541 getInputStream :java.io.FileInputStream@1f460230

練習28 自定義攔截器

springMVC可使用攔截器對請求進行攔截,用戶能夠自定義攔截器來實現特定的需求,自定義的攔截器必須實現HandlerInterceptor接口。接口中須要實現三個方法
1. preHandler() 這個方法再調用目標方法以前被執行,在該方法中能夠對用戶請求request 進行處理,若是須要在執行這個方法以後還須要調用其餘攔截器,或者目標方法,則必須返回true。不然返回false。
2. postHandler() 這個方法是在目標方法執行完後,且在試圖渲染以前執行(也就是DispatchServlet向客戶端返回響應前調用),在該方法中對request進行處理。
3. afterCompletion() 這個方法在DispatchServlet徹底處理完請求後被調用,能夠在該方法中進行一些資源清理的工做。
練習
STEP1 實現HandlerInterceptor接口的實現類MyFirstInterceptor和MySecondInterceptor
MySecondInterceptor和MyFirstInterceptor同樣,本身寫。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
public class MyFirstInterceptor implements HandlerInterceptor{ /** * 執行時間:在調用目標方法以前調用 * 返回true,執行後續方法,返回false,則都不回執行. * 做用:能夠用做 權限,日誌,事務等. * * 特殊狀況說明: * 在多個攔截器出現的時候,若是最後的攔截器返回false,則任然會執行上一個攔截器會的afterCompletion方法 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyFirstInterceptor preHandle" ); return true; } /** * 執行時間:調用目標方法以後,渲染視圖事前 * 做用:能夠對請求域中的屬性或視圖進行修改 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyFirstInterceptor postHandle" ); } /** * 實現時間:渲染視圖以後調用 * 做用:釋放資源 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyFirstInterceptor afterCompletion" ); } }

STEP2 springMVC增配

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
<mvc:interceptors> <bean class="com.iboray.smt.controller.MyFirstInterceptor"></bean> <!--能夠對攔截器作更多設置--> <mvc:interceptor> <!--表明對/user路勁起做用--> <mvc:mapping path="/user/**"/> <bean class="com.iboray.smt.controller.MySecondInterceptor"></bean> </mvc:interceptor> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean> </mvc:interceptors>

執行/user下的全部目標方法,看後臺打印結果,我執行的是<a href="/user/i18n?locale=zh_CN">i18n_Change_zh_CN</a>這個方法,例子 再練習26 國際化中。
後臺打印

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//順序執行 MyFirstInterceptor preHandle MySecondInterceptor preHandle //目標方法打印 用戶名 //反序執行 MySecondInterceptor postHandle MyFirstInterceptor postHandle //反序執行 MySecondInterceptor afterCompletion MyFirstInterceptor afterCompletion

爲何會是這個順序呢?
由於spring默認會按配置文件是順序加載自定義的攔截器
如圖:
這裏寫圖片描述
爲了進一步瞭解攔截器執行順序,參考下圖(來自網絡)
狀況1 first和second攔截器的preHandle方法都返回true,也就是正常實行
這裏寫圖片描述
狀況2 first攔截器的preHandle方法返回true,second攔截器的preHandle方法返回false,執行流程爲實線。
這裏寫圖片描述

練習29 異常處理

springMVC經過HandlerExceptionResolver處理程序異常,包括Handler映射,數據綁定以及目標方法執行時發生的異常。
springMVC提供了HandlerExceptionResolver的實現類
這裏寫圖片描述

DispatchServlet默認裝配的HandlerExceptionResolver若是使用<mvc:annotation-driven>,則實現類爲
1. ExceptionHandlerExceptionResolver(若是不使用<mvc:annotation-driven>,默認加載的是AnnotationMethodHandlerExceptionResolver)
2. ResponseStatusExceptionResolver
3. DefaultHandlerExceptionResolver
這裏寫圖片描述

ExceptionHandlerExceptionResolver

主要處理Handler中@ExceptionHandler註解定義的方法
@ExceptionHandler有處理優先級的問題,如發生的是NullPointerException,但聲明的是RuntimeException和Exception,此時會根據異常的最近繼承關係找到繼承深度最淺的那一個@ExceptionHandler註解方法,也就是標記了RuntimeException的方法。
ExceptionHandlerMethodResolver內部若找不到@ExceptionHandler註解的話,會找@ControllerAdvice中的@ExceptionHandler註解方法。而且也會有優先級的問題。
小練習
STEP1 controller加入目標方法和@ExceptionHandler方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
/** * @ExceptionHandler 標註的方法,能夠在入參中加入 Exception類型的參數,該參數即對應發生的異常對象. * 若但願將異常對象傳到頁面上,則須要返回ModelAndView,而不能是Map */ @ExceptionHandler({NullPointerException.class}) public ModelAndView testExceptionHandlerExceptionResolver(Exception ex){ ModelAndView mv = new ModelAndView("error"); mv.addObject("ex",ex); return mv; } //測試的目標方法 @RequestMapping("/testMatch") public String testMatch(@RequestParam("a") int a ){ System.out.println( 10 / a); return "index"; }

STEP2 ControllerAdvice標記的類
須要注意的是,這個類的@ExceptionHandler方法中放入的異常比上面controller中的異常要「小」,也就是繼承級別很淺。那若是發生ArithmeticException.class異常,對應處理方法是MyExceptionHandle的testExceptionHandlerExceptionResolver方法。

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
@ControllerAdvice public class MyExceptionHandle { @ExceptionHandler({ArithmeticException.class}) public ModelAndView testExceptionHandlerExceptionResolver(Exception ex){ System.out.println("MyExceptionHandle"+ex); ModelAndView mv = new ModelAndView("error"); mv.addObject("ex",ex); return mv; } }

STEP3 error界面承接exception對象

  
  
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
<body> ${ex} </body>

STEP4 測試

  
  
  
  
  • 1
  • 1
<a href="/user/testMatch?a=0" >testMatch</a>

結果打印的是

  
  
  
  
  • 1
  • 1
MyExceptionHandle java.lang.ArithmeticException: / by zero

ResponseStatusExceptionResolver

在異常及異常父類中找到@ResponseStatus註解,而後使用這個註解的屬性進行處理
定義一個@ResponseStatus註解修飾的異常類
若再處理器方法中拋出@ResponseStatus修飾的類類型,若ExceptionHandlerExceptionResolver不解析異常,因爲觸發的異常的類帶有@ResponseStatus註解,所以會被ResponseStatusExceptionResolver解析到,最後響應HttpStatus.XXXX代碼給客戶端,關於其餘響應代碼參考org.springframework.http.HttpStatus枚舉類

小練習

STEP1 添加@ResponseStatus標註的類

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
@ResponseStatus(value = HttpStatus.NOT_ACCEPTABLE,reason = "非法受權") public class MyResponseStatusException extends RuntimeException { }

STEP2 添加controller目標方法

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
/** *注意:若是將@ResponseStatus放到這裏,那麼執行完這個方法後會直接返回錯誤狀態碼和緣由。 *這樣就不用新建那個本身的異常處理類(MyResponseStatusException)了。能夠根據自身的業務來作調整。 **/ @RequestMapping("/testResponseStatusException") public String testResponseStatusException(@RequestParam("a") int a){ if (a == 10){ throw new MyResponseStatusException(); } System.out.println("testResponseStatusException .. . . . ."); return "index"; }

STEP3 測試

  
  
  
  
  • 1
  • 1
<a href="/user/testResponseStatusException?a=1" >testResponseStatusException</a>

結果
這裏寫圖片描述

源碼分析

進入doResolveException方法
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#doResolveException

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //找到咱們標記@ResponseStatus的類或方法 ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { //執行resolveResponseStatus return resolveResponseStatus(responseStatus, request, response, handler, ex); } catch (Exception resolveEx) { logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); } } return null; }

進入resolveResponseStatus
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#resolveResponseStatus

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //這是註解的兩個屬性,一個是狀態,一個是異常描述 int statusCode = responseStatus.value().value(); String reason = responseStatus.reason(); if (this.messageSource != null) { reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()); } if (!StringUtils.hasLength(reason)) { //沒有描述就直接寫入response response.sendError(statusCode); } else { //不然重載sendError方法,把狀態碼和緣由一塊寫入 response.sendError(statusCode, reason); } return new ModelAndView(); }

DefaultHandlerExceptionResolver

這個異常解析器是對一些特殊的異常進行處理的。如
HttpRequestMethodNotSupportedException
MissingServletRequestParameterException
ServletRequestBindingException
ConversionNotSupportedException
….等。是否是似曾相識呢?對,這些就是springMVC本身的一些異常處理方法。

小練習

STEP1 controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//重點是POST請求 @RequestMapping(value = "/testDefaultHandlerExceptionResolver",method = RequestMethod.POST) public String testDefaultHandlerExceptionResolver(){ System.out.println("testDefaultHandlerExceptionResolver .. . . . ."); return "index"; }

STEP2 請求

  
  
  
  
  • 1
  • 2
  • 1
  • 2
<!--明顯是個GET請求--> <a href="/user/testDefaultHandlerExceptionResolver" >testDefaultHandlerExceptionResolver</a>

結果:
這裏寫圖片描述

源碼分析

進入
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof NoSuchRequestHandlingMethodException) { return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response, handler); } else if (ex instanceof HttpRequestMethodNotSupportedException) { //這裏就返回那個錯誤頁面 return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, response, handler); } ... }

SimpleMappingExceptionResolver

SimpleMappingExceptionResolver能夠對全部異常進行統一處理,它將異常名映射爲視圖名,也就是說。捕獲異常後,輸出到指定的視圖。
小練習
STEP1 增長controller

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@RequestMapping("/testSimpleMappingExceptionResolver") public String testSimpleMappingExceptionResolver(@RequestParam("a") String a){ Integer.parseInt(a); System.out.println("testSimpleMappingExceptionResolver .. . . . ."); return "index"; }

STEP2 測試 未配置異常解析器

  
  
  
  
  • 1
  • 1
<a href="/user/testSimpleMappingExceptionResolver?a=a" >testSimpleMappingExceptionResolver</a>

結果
這裏寫圖片描述
STEP3測試 配置異常解析器
增長springMVC配置

  
  
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
<!--使用簡單映射異常解析器--> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--對應視圖的異常參數名--> <property name="exceptionAttribute" value="ex"></property> <!--異常及對應的視圖--> <property name="exceptionMappings"> <props> <!--這個異常指向error視圖--> <prop key="java.lang.NumberFormatException">error</prop> </props> </property> </bean>

再進行STEP2 測試,結果:
這裏寫圖片描述

最後想說的

學在苦中求,藝在勤中練。

相關文章
相關標籤/搜索