1. 程式人生 > 實用技巧 >SpringMVC(SpringMVC的強大註解們)

SpringMVC(SpringMVC的強大註解們)

1.springMVC介紹

概述:Spring為檢視層提供的基於MVC設計理念的優秀的Web框架,是目前最主流的MVC框架之一,SpringMVC通過一套MVC註解,讓POJO成為處理請求的處理器,而無須實現任何介面,它也支援REST風格的URL請求;它採用了鬆散耦合的可插拔元件結構,比其他MVC框架更具擴充套件性和靈活性

2.springMVC的第一個程式

1.導包

  • commons-logging-1.1.3.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar
  • spring-web-4.0.0.RELEASE.jar
  • spring-webmvc-4.0.0.RELEASE.jar

2.寫配置

  • web.xml
  • springmvc的配置檔案

3.測試

程式碼實現:

web.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

springMVC的配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	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.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	<!-- 配置一個檢視解析器,能幫我們拼接頁面地址 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
</beans>

jsp頁面:

//index.jsp

...
<body>
	<a href="hello">HelloWorld</a>
</body>
...

//success.jsp
...
<body>
	訪問成功
</body>
...

java類(處理請求):

package com.luyi.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 告訴Spring這是一個處理器,可以處理請求
 * @Controller:標識哪個元件是控制器
 * @author luyi
 *
 */

@Controller
public class HelloController {
	
	/**
	 *  /代表當前專案下:處理當前專案下的hello請求
	 */
	@RequestMapping("/hello")
	public String helloRequest(){
		System.out.println("請求收到了,正在處理。。。");
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}
}

3.springMVC的一些細節

執行流程

  • 客戶端點選“HelloWorld”超連結會發送http://localhost:8080/springMVC_01/hello請求
  • 來到tomcat伺服器
  • SpringMVC的前端控制器收到請求
  • 來看請求地址和@RequestMapping標註的哪個匹配,來找找到底使用哪個類的哪個方法
  • 前端控制器找到了目標處理器類和目標方法,直接利用返回來執行目標方法
  • 方法執行完成以後會有一個返回值,SpringMVC認為這個返回值就是要去的頁面地址
  • 拿到方法返回值以後,用檢視解析器進行拼串得到完整的頁面地址
  • 拿到頁面,前端控制器幫我們轉發到頁面

@RequestMapping的使用

概述:這個註解就是為了告訴springMVC,這個方法用來處理什麼請求;這個/可以省略,即使省略了,也就是預設從當前專案下開始,習慣加上比較好;一個方法只能處理一個請求,如果兩個方法都去處理一個請求,就會報錯

@RequestMapping可以標註在控制器的類定義及方法定義處:

  • 類定義處:提供初步的請求對映資訊,相對於web應用的根目錄
  • 方法處:提供進一步的細分對映資訊,相對於類定義處的url,若類定義出為標註@RequestMapping,則方法處標誌的URL相對於WEB應用的根目錄

@RequestMapping的幾個屬性:

  • value:就是處理的請求路徑,如value="/handle"

  • method:就是規定請求方式(get,post等),是對應的請求方式才處理,不是則報405錯,如method=RequestMethod.GET

  • params:規定請求引數,不滿足則報錯404

    • param1:表示請求必須包含有名為param1的請求引數,如params={"username"}
    • !param1:表示請求不能包含名為param1的請求引數,如params={"!username"}
    • param1 != value1:表示請求包含名為param1的請求引數,但其值不能為value1,如params={"username!=123"}
    • param1 = value1, param2:表示多個條件,如params={"username!=123", "pwd", "!age"}
  • headers:規定請求頭,如headers={"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"}

  • consumes:只接受內容型別是某種的請求,規定請求頭中的Content-Type

  • produce:告訴瀏覽器返回的內容型別是什麼,給響應頭中加上Content-Type:text/html;charset=utf-8

@RequestMapping的RequestMapping-ant風格的url(@RequestMapping的模糊匹配功能):

  • ?:能替代任意一個字元
  • *:能替代任意多個字元,和一層路徑
  • **:能替代多層路徑

eg:

@RequestMapping(value="/hello0?")
@RequestMapping(value="/*/hello")
@RequestMapping(value="/**/hello")

@PathVariable獲取路徑上的佔位符:

@RequestMapping(value="/hello/{id}")
	public String helloRequest(@PathVariable("id")String id){
		System.out.println(id);
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}

指定配置檔案位置

概述:如果不指定配置檔案的位置,它就會預設去找/WEB-INF/下的 前端控制名-servlet.xml檔案,如springDispatcherServlet-servlet.xml

web.xml中的url-pattern配置

處理*.jsp頁面是tomcat做的事,所有專案的小web.xml都是繼承於大web.xml:

  • 伺服器的大web.xml中有一個DefaultServlet是url-pattern=/,訪問靜態資源時,如果我們走的是DefaultServlet的url-pattern,那tomcat就會在伺服器下找到這個資源並返回;而我們配置的前端控制器的/覆蓋了tomcat伺服器中的DefaultServlet,因此我們訪問不到靜態資源
  • 我們的配置中前端控制器url-pattern=/
  • 我們配置url-pattern=/卻能訪問到jsp頁面是因為我們沒有覆蓋伺服器中大web.xml中的jspServlet的配置
  • /* 直接就是攔截所有請求,所以我們寫/,也是為了迎合後面的Rest風格的url地址

SpringMVC獲取請求帶來的各種資訊的方式:

1.獲取請求引數

1).預設方式獲取請求引數:直接給方法引數上寫一個和請求引數名相同的變數,這個變數就來接收請求引數的值,如果請求帶這個引數,但是沒有值(如user=),則這個引數為空串,如果請求沒有帶引數,則為null

2).@RequestParam:獲取請求引數的,引數預設是必須帶的,如@RequestParam("user")String use,如果請求沒帶這個引數就會報錯,@RequestParam還有三個屬性,分別是:

  • value:指定要獲取的引數的key
  • required:這個引數是否必須的
  • defaultValue:預設值,沒有這個屬性的話預設值是null

注意:@RequestParam是用來獲取引數的,獲取不了路徑的值,而@PathVariable則是用來獲取路徑的,獲取不到引數的值

2.獲取請求頭

  • @RequestHeader,如@RequestHeader("User-Agent") String userAgent,如果獲取沒有這個請求頭,然後去獲取,就會報錯

注意:@RequestHeader也有三個屬性,value,required和defaultValue

3.獲取cookie值

  • @CookieValue:獲取某個cookie的值,去獲取沒有這個cookie名的cookie也會報錯

注意:@RequestHeader也有三個屬性,value,required和defaultValue

請求處理

1.傳入POJO

概述:如果傳入的引數是一個POJO,那麼springMVC會自動的為這個POJO進行封裝賦值

  • 將POJO中的每一個屬性,從request引數中嘗試獲取出來,並封裝即可
  • 還可以級聯封裝

程式碼示例:

index.jsp頁面:

<form action="book" method="post">
		書名:<input type = "text" name = "bookName"><br>
		作者:<input type = "text" name = "author"><br>
		價格:<input type = "text" name = "price"><br>
		<hr>
		省份:<input type = "text" name = "address.provice"><br>
		市:<input type = "text" name = "address.city"><br>
		<input type="submit" value="提交資料">
	</form>

BookController.java檔案:

package com.luyi.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.luyi.dao.Book;

@Controller
public class BookController {
	@RequestMapping(value="/book", method=RequestMethod.POST)
	public String addBook(Book book){
		System.out.println("我要儲存的圖書" + book);
		return "success";
	}	
	
}

POJO類:

//Book.java

package com.luyi.dao;

public class Book {
	private String bookName;
	private String author;
	private Address address;
	private Integer price;
	
	public Book(){};
	
	
	
	public Book(String bookName, String author, Address address, Integer price) {
		super();
		this.bookName = bookName;
		this.author = author;
		this.address = address;
		this.price = price;
	}



	

	public String getBookName() {
		return bookName;
	}



	public void setBookName(String bookName) {
		this.bookName = bookName;
	}



	public String getAuthor() {
		return author;
	}



	public void setAuthor(String author) {
		this.author = author;
	}



	public Address getAddress() {
		return address;
	}



	public void setAddress(Address address) {
		this.address = address;
	}



	public Integer getPrice() {
		return price;
	}



	public void setPrice(Integer price) {
		this.price = price;
	}



	@Override
	public String toString() {
		return "Book [bookName=" + bookName + ", author=" + author
				+ ", address=" + address + ", price=" + price + "]";
	}
	
	
	
}

//Address.java

package com.luyi.dao;

public class Address {
	private String provice;
	private String city;
	
	public Address(){};
	
	public Address(String provice, String city) {
		super();
		this.provice = provice;
		this.city = city;
	}
	
	public String getProvice() {
		return provice;
	}
	public void setProvice(String provice) {
		this.provice = provice;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	@Override
	public String toString() {
		return "Address [provice=" + provice + ", city=" + city + "]";
	}
	
	
}

注意:傳送請求和響應資料如果有中文字元會有亂碼現象,解決方案如下:

request亂碼:

  • get請求:修改server.xml檔案:URIEncoding="UTF-8"

  • post請求:在第一次獲取請求引數之前設定request.setCharacterEncoding("UTF-8");springMVC中有一個過濾器就在幫我們做這樣的事情,需要注意的是這個字元編碼過濾器的配置一般在其他過濾器之前 ,關於這個過濾器的配置如下:

     <filter>
     	<filter-name>CharacterEncodingFilter</filter-name>
     	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
     	<init-param>
     		<!-- encoding:指定解決POST請求亂碼 -->
     		<param-name>encoding</param-name>
     		<param-value>UTF-8</param-value>
     	</init-param>
     	<init-param>
     		<!-- forceEncoding:解決了響應亂碼問題 -->
     		<param-name>forceEncoding</param-name>
     		<param-value>true</param-value>
     	</init-param>
     	
     </filter>
     <filter-mapping>
     	<filter-name>CharacterEncodingFilter</filter-name>
     	<url-pattern>/*</url-pattern>
     </filter-mapping>
     
     <filter>
     	<filter-name>HiddenHttpMethodFilter</filter-name>
     	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
     </filter>
     <filter-mapping>
     	<filter-name>HiddenHttpMethodFilter</filter-name>
     	<url-pattern>/*</url-pattern>
     </filter-mapping>
    

response亂碼:response.setContentType("text/html;charset=utf-8")

2.傳入原生API

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale:與國際化有關的區域資訊物件
  • InputStream
  • OutputStream
  • Reader
  • Writer

程式碼示例:

@RequestMapping(value="/handle")
	public String handle(HttpSession session, HttpServletRequest request){
		session.setAttribute("sessionParam", "session域中的東西");
		request.setAttribute("requestParam", "request域中的東西");
		return "success";
	}

資料輸出

1.傳入Map,Model,ModelMap型別的引數

2.方法返回值返回ModelAndView型別,既包含模型資料(給頁面帶的資料),而且資料是放在請求域中的

3.springMVC還提供了一種可以臨時給Session域中儲存資料的方式,使用一個註解@SessionAttribute,只能標在類上,這個註解的屬性value指定儲存資料時要給session中放的資料的key,types指定只要儲存的是這種型別的資料,給session中也放一份,不推薦使用,可能會引發異常,給session中放資料還是推薦使用原生API

4.@ModelAttribute註解的應用場景是在修改資料時不用全部修改所有資料時使用,後來有了Mybatics之後,應用場景就不多了;當我們傳入一個POJO時,SpringMVC會幫我們封裝這個POJO,springMVC自動封裝這個POJO的原理就是幫我們new了一個這樣的物件出來,而new出來的物件如果有屬性是沒有賦值的話,那麼就會是null值,但是我們的應用場景是想要它是之前保留的資料,此時,@ModelAttribute就派上了用場

程式碼示例:

//TestController.java

package com.luyi.controller;

import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController {
	
	@RequestMapping("/handle01")
	public String handle01(Map<String, String> map){
		map.put("msg1", "map是可以的");
		return "success";
	}
	
	@RequestMapping("/handle02")
	public String handle02(Model model){
		model.addAttribute("msg2", "model是可以的");
		return "success";
	}
	
	@RequestMapping("/handle03")
	public String handle03(ModelMap modelMap){
		modelMap.addAttribute("msg3", "ModelMap是可以的");
		return "success";
	}
	
	/**
	 * 返回值是ModelAndView,可以為頁面攜帶資料
	 * @return
	 */
	@RequestMapping("/handle04")
	public ModelAndView handle04(){
		ModelAndView mv = new ModelAndView();
		mv.setViewName("success");
		mv.addObject("msg4", "方法返回值為ModelAndView型別");
		return mv;
	}
}

//success.jsp
<body>
<h1>成功跳轉頁面</h1>
	${requestScope.msg1}
	${requestScope.msg2}
	${requestScope.msg3}
	${requestScope.msg4}
</body>

檢視解析

概述:檢視解析器只是為了得到檢視物件,檢視物件才是真正的轉發(將模型資料全部放在請求中)或者重定向到頁面,檢視物件才是真正的渲染檢視

1.forward字首指定一個轉發操作

程式碼示例:

@RequestMapping("/handle")
	public String handle(){
		/**
		 * 當我們已經配置了檢視解析器,字首為/WEB-INF/,字尾為.jsp時,
		 * 如果我們想要傳送一個從根路徑開始的請求的話,有兩種方法:
		 *1)使用相對路徑../../
		 *2)forward:字首
		 */
		return "../../index";
	}

2.redirect字首指定重定向到頁面

程式碼示例:

@RequestMapping("/handle01")
	public String handle01(){
		//重定向到hello.jsp頁面
		return "redirect:/hello.jsp";
	}
	
	@RequestMapping("/handle02")
	public String handle02(){
		//重定向到/handle01請求(多次重定向)
		return "redirect:/handle01";
	}

3.檢視和檢視解析器

  • 請求處理方法執行完成後,最終都會返回一個ModelAndView物件,對於那些返回String,View,或ModelMap等型別的處理方法,SpringMVC也會在內部將他們裝配成一個ModelAndView物件,它包含了邏輯名和模型物件的檢視
  • SpringMVC藉助檢視解析器(ViewResolver)得到最終的檢視物件(View),最終的檢視可以是jsp,也可能是Excel,JFreeChart等各種表現形式的檢視
  • 對於最終究竟採取何種檢視物件對模型資料進行渲染,處理器並不關係,處理器工作重點聚焦在生產模型資料的工作上,從而實現MVC的充分解耦

4.jstlView支援國際化功能

步驟:

  • 導包匯入了jstl的時候會自動建立為一個jstlView,可以快速方便的支援國際化功能;
  • 快速國際化:
    • javaWeb國際化步驟:
      • 得得到一個locale物件
      • 使用ResourceBundle繫結國際化資原始檔
      • 使用ResourceBundle.getString("key")來獲取國際化配置檔案中的值
      • web頁面的國際化,fmt標籤來做(fmt:setLocale,fmt:setBundle,fmt:message
    • 有了jstlView以後:
      • 讓spring管理國際化資源就行
      • 直接去頁面使用fmt:message
        程式碼示例:

index.jsp(首頁):

<body>
<a href="toLoginPage">login</a>
</body>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 配置一個字元編碼的Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<!-- encoding:指定解決POST請求亂碼 -->
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<!-- forceEncoding:解決了響應亂碼問題 -->
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<filter>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>HiddenHttpMethodFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	
</web-app>

springMVC.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	
	<!-- 匯入jstlView包,可以支援便捷的國際化功能,還有一種就是直接設定viewClass屬性值為JstlView也行 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
	</bean>
	
	<!-- 讓springMVC管理國際化資原始檔:配置一個資原始檔管理器 ,id名必須為messageSource-->
	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<!-- basename指定基礎名-->
		<property name="basename" value="i18n"></property>
	</bean>
</beans>

TestController.java(前端控制器)

package com.luyi.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
	
	@RequestMapping("/toLoginPage")
	public String toLoginPage(){
		return "login";
	}
	
}

國際化資原始檔:

i18n_en_US.properties:

welcomeinfo=WELCOME TO LOGIN PAGE
username=USERNAME
password=PASSWORD
loginBtn=LOGIN

i18n_zh_CN.properties:

welcomeinfo=\u6B22\u8FCE\u6765\u5230\u767B\u5F55\u9875\u9762
username=\u7528\u6237\u540D
password=\u5BC6\u7801
loginBtn=\u63D0\u4EA4

注意:要想達到國際化的效果,一定要過springMVC的檢視解析流程,人家會建立一個JstlView幫你快速國際化,也不能寫forward

5.在spring配置檔案中將一個請求對映到一個頁面

程式碼示例:

<!-- 傳送一個請求,直接來到web-inf下的頁面login頁面,需要mvc名稱空間 -->
<!-- path="":指定哪個請求 -->
<!-- view-name:指定對映到哪個檢視 -->
<mvc:view-controller path="/toLoginPage" view-name="login" />
<!-- 開啟MVC註解驅動模式 ,如果沒有這一段程式碼,
則除了toLoginPage請求以外,其他的請求都會請求不到資料了-->
<mvc:annotation-driven></mvc:annotation-driven>

4.知識點補充之REST

概述:REST也就是Representational State Transfer(表現層狀態轉化),是目前最流行的一種網際網路軟體架構,它結構清晰、符合標準、易於理解,擴充套件方便,所以正得到越來越多網站的採用

REST推薦的url命名方式:/資源名/資源識別符號

  • /book/1:請求方式為get,查詢1號圖書
  • /book/1: 請求方式為delete,刪除1號圖書
  • /book/1: 請求方式為put,修改1號圖書
  • /book:請求方式為post,增加1號圖書

那麼,如何從頁面發起put和delete形式的請求呢?Spring非常友好的為我們提供了發起put和delete請求的支援,只要按步驟來即可搞定put和delete請求

  • 首先,SpringMVC中有一個Filter(HiddenHttpMethodFilter),它可以把普通的請求轉化為規定形式的請求,配置這個filter
  • 其次,建立一個post型別的表單
  • 最後,表單項中攜帶一個_method的引數,而這個_method的值就是DELETE,PUT

程式碼示例:

配置檔案:

//springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	<!-- 配置一個檢視解析器,能幫我們拼接頁面地址 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
</beans>

//web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 使得其支援REST風格 -->
	<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>
</web-app>

jsp檔案:

//index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isErrorPage="true"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a href="book/1">查詢圖書館</a><br>
	<form action="book" method="post">
		<input type="submit" value="新增圖書"/>
	</form><br>
	<form action="book/1" method="post">
		<input name="_method" value="delete"/>
		<input type="submit" value="刪除1號圖書"/>
	</form><br>
	<form action="book/1" method="post">
		<input name="_method" value="put"/>
		<input type="submit" value="修改1號圖書"/>
	</form><br>
</body>
</html>

//success.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	操作成功
</body>
</html>

java類檔案:

//HelloController

package com.luyi.controller;

import org.apache.catalina.connector.Request;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 告訴Spring這是一個處理器,可以處理請求
 * @Controller:標識哪個元件是控制器
 * @author luyi
 *
 */

@Controller
public class HelloController {
	
	/**
	 *  /代表當前專案下:處理當前專案下的hello請求
	 */
	@RequestMapping(value="/book/{id}", method=RequestMethod.GET)
	public String getBook(@PathVariable("id")String id){
		System.out.println("查詢了" + id + "號書");
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}
	
	@RequestMapping(value="/book", method=RequestMethod.POST)
	public String addBook(){
		System.out.println("添加了一本圖書");
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}
	
	@RequestMapping(value="/book/{id}", method=RequestMethod.PUT)
	public String setBook(@PathVariable("id")String id){
		System.out.println("修改了" + id + "號圖書");
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}
	
	@RequestMapping(value="/book/{id}", method=RequestMethod.DELETE)
	public String deleteBook(@PathVariable("id")String id){
		System.out.println("刪除了" + id + "號圖書");
		//檢視解析器解析成/WEB-INF/page/success.jsp
		return "success";
	}
}

注意:高版本的tomcat(8.0以上)對REST的支援有點問題,對put和delete的請求會報405錯誤,解決方法是在jsp頁面中把isErrorPage設定為true(isErrorPage="true")

5.springMVC原始碼知識

springMVC的整個執行大致流程:

1).所有請求過來DispatcherServlet收到請求

2).呼叫doDispatch()方法進行處理

  • getHandler():根據當前請求地址找到能處理這個請求的目標處理器類(處理器),根據當前請求在HandleMapping中找到這個請求的對映資訊,獲取到目標處理器類
  • getHandlerAdapter():根據當前處理器類獲取到能執行這個處理器方法的介面卡;
  • 使用剛才獲取到的介面卡(AnnotationMethodHandlerAdapter)執行目標方法
  • 目標方法執行後會返回一個ModelAndView物件
  • 根據ModelAndView的資訊轉發到具體的頁面,並可以在請求域中取出ModelAndView中的模型資料

DispatcherServlet中有幾個引用型別的屬性:SpringMVC的九大元件(在springMVC工作的時候,關鍵位置都是用九大元件完成的):

  • MultipartResolver:檔案上傳解析器
  • LocaleResolver:區域資訊解析器,和國際化有關
  • ThemeResolver:主題解析器,強大的主題效果更換
  • List:handler對映資訊
  • List:Handler的介面卡
  • List:springMVC強大的異常解析功能,異常解析器
  • RequestToViewNameTranslator:當沒有返回檢視名時,請求地址就轉換為檢視名
  • FlashMapManager:SpringMVC中執行重定向攜帶資料的功能
  • List:檢視解析器

注意:九大元件都是介面,介面就是規範,提供了非常強大的擴充套件性

確定處理請求的方法的每個引數的值

1.標了註解:儲存註解的資訊,最終得到這個註解應該對應解析的值

2.沒標註解:

  • 看是否是原生API

  • 看是否是Model或者Map等

  • 看是否是簡單型別;paramName;

  • 以上都不是的話,看是否是給attrName賦值;attrName(引數標了@ModelAttribute("")就是指定的,沒標的話就是空串),確定自定義型別引數:

    • 先看隱含模型中有沒有這個attrName作為key對應的值,如果有就從隱含模型中獲取並賦值
    • 看是否是@SessionAttributes標註的屬性,如果是,從Session中拿,如果拿不到就會拋異常
    • 如果以上都不是,則利用反射建立一個新的 物件
  • 拿到之前建立好的物件,使用資料繫結器(WebDataBinder)將請求中的每個資料繫結到這個物件中

6.springMVC的資料繫結

資料轉換&資料格式化&資料校驗

概述:當我們前端頁面提交了資料之後,springMVC封裝自定義型別物件的時候,javaBean要和頁面提交資料繫結,就需要牽扯到三種操作,資料繫結期間的資料轉換(如:String-->Integer),資料格式化問題(如日期的表達形式有多種:2020-8-25,2020.8.25等),以及前端資料和後端資料的校驗(如我們提交的資料是否合法:郵箱格式,手機號碼等都有一定的格式)

WebDataBinder

概述:WebDataBinder為資料繫結器,負責資料的繫結工作,資料繫結期間產生的型別轉換,格式化,資料校驗等問題都由他來搞定

在WebDataBinder中,有幾個元件分別負責繫結期間的不同的問題:

  • ConversionService元件:負責資料型別的轉換以及格式化功能

  • Validators元件:負責資料的校驗

  • BindingResult元件:負責儲存以及解析資料繫結期間資料產生的錯誤

資料繫結流程

  • SpringMVC主框架將ServletRequest物件及目標方法的入參例項傳遞給WebDataBinderFactory例項,以建立DataBinder例項物件
  • DataBinder呼叫裝配在SpringMVC上下文中的ConversionService元件進行資料型別轉換、資料格式化工作,將Servlet中的請求資訊填充到入參物件中
  • 呼叫Validator元件對已經綁定了請求資訊的入參物件進行資料合法性校驗,並最終生產資料繫結結果BindingData物件
  • SpringMVC抽取BindingResult中的入參物件和校驗錯誤物件,將它們賦給處理方法的響應入參

如圖所示:

資料轉化--自定義型別轉化器

步驟:

  • 實現Converter介面(Converter介面是ConversionService接口裡面的),做一個自定義型別的轉換器

  • 配置出ConversionService:

    • 自定義的Converter得放進ConversionService中;
    • 將WebDataBinder中的ConversionServlet設定成我們這個加了自定義型別轉換器的COnversionService
  • 讓springMVC用我們的ConversionService

程式碼實現:

1.實現Converter介面:

package com.luyi.component;

import org.springframework.core.convert.converter.Converter;

import com.luyi.dao.Book;

/**
 * 實現Converter<S, T>介面,泛型S為要轉換的目標,T為要轉換為什麼
 * @author luyi
 *
 */
public class MyConverter implements Converter<String, Book>{

	@Override
	public Book convert(String arg0) {
		//兩者相互轉換的一些規則
		...
		return null;
	}

}

2.配置出ConversionService

<!-- 告訴springMVC別用預設的ConversionService,
 而是用我們自定義的ConversionService,裡面有我們自定義的Converter-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
	<!-- 往converters轉換器(一個set集合)中新增我們自定義的型別轉換器 -->
	<property name="converters">
		<set>
			<bean class="com.luyi.component.MyConverter"></bean>
		</set>
	</property>
	
</bean>	

3.讓springMVC用我們的ConversionService

<!-- 開啟MVC註解驅動模式 ,如果沒有這一段程式碼,
則除了toLoginPage請求以外,其他的請求都會請求不到資料了,
讓springMVC用我們的ConversionService(id為conversionService)-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

mvc:annotation-driven標籤的解析

<mvc:annotation-driven /> 會自動註冊RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 與 ExceptionHandlerExceptionResolver三個bean。
還將提供以下支援:

  • 支援使用 ConversionService 例項對錶單引數進行型別轉換
  • 支援使用 @NumberFormat annotation、@DateTimeFormat 註解完成資料型別的格式化
  • 支援使用 @Valid 註解對 JavaBean 例項進行 JSR 303 驗證
  • 支援使用 @RequestBody 和 @ResponseBody 註解

<mvc:annotation-driven />標籤與<mvc:default-servlet-handler /> 標籤的配合使用:

  • 如果這兩個標籤都不配置上的話,動態資源(@RequestMapping對映的資源能訪問到,靜態資源(.html,.js,.img等)訪問不到)
  • 只加了<mvc:default-servlet-handler />標籤,不加<mvc:annotation-driven />標籤,則可以訪問靜態資源,但動態資源訪問不到
  • 兩個標籤都加上的話,則靜態資源和動態資源都能訪問得到

資料格式化

日期格式化的註解:@DateTimeFormat(pattern="yyyy-MM-dd")

程式碼示例:

//日期格式化,轉換為yyyy-MM-dd格式
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date brith;

除了日期格式化,還有數值的格式化,程式碼示例如下:

//數值的格式化,格式如10,000.66
@NumberFormat(pattern="#,###.##")
private Integer price;

注意:如果我們前面有配置自定義的Converter,如果bean配置的classs是ConversionServiceFactoryBean,則日期格式化註解仍然沒有起作用,他沒有走預設的Converters,而為了可以走預設的Converters,又可以實現我們自己自定義的Converter,則需要配置bean的class為FormattingConversionServiceFactoryBean,該類內部已經註冊了 :

  • NumberFormatAnnotationFormatterFactroy:支援對數字型別的屬性使用 @NumberFormat 註解
  • JodaDateTimeFormatAnnotationFormatterFactroy:支援對日期型別的屬性使用 @DateTimeFormat 註解

資料校驗

概述:資料校驗只做前端校驗是不安全的,在重要的資料上一定要加上後端驗證,實現前端校驗的方法:

  • 可以寫程式將我們每一個數據取出進行校驗,如果失敗直接來到新增頁面,提示其重新填寫
  • SpringMVC:可以使用JSR303來做資料校驗

如何快速的進行後端校驗:

1).匯入校驗框架的jar包:

  • hibernate-validator-5.0.0.CR2.jar
  • hibernate-validator-annotation-processor-5.0.0.CR2.jar
  • classmate-0.8.0.jar
  • jboss-logging-3.1.1.GA.jar
  • validation-api-1.1.0.CR1.jar

注意:從下載來的hibernate-validator-5.0.0.CR2資料夾中的required資料夾下中還有幾個帶el的jar包(el-api-2.2.jar,javax.el-2.2.4.jar
,javax.el-api-2.2.4.jar),我們沒有匯入進去,原因是tomcat7.0版本以上el表示式比較強大,不用匯入這幾個帶el的jar包,如果你使用的是tomcat7.0版本以下的,那麼就需要將帶el的幾個jar包放在tomcat的lib資料夾下

2).導完包後只需要給javaBean的屬性新增上校驗註解即可

3).在springMVC封裝物件的時候,告訴SpringMVC這個javaBean需要校驗,使用@Valid註解即可達到這個效果

4).如何知道校驗結果:給需要校驗的javaBean後面緊跟一個BindingResult,這個BindingResult就封裝了bean的校驗結果

5).根據校驗結果,執行相應的操作,form:errors標籤可以回顯錯誤

程式碼示例:

index.jsp(首頁):

<body>
	<a href="toAddBookPage">新增圖書</a>
</body>

Book.java(給javaBean的屬性新增上校驗註解):

package com.luyi.dao;

import java.util.Date;

import javax.validation.constraints.Past;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;

public class Book {
	@NotEmpty
	private String bookName;
	@Length(min=6, max=18)
	private String author;
	@Email
	private String authorEmail;
	
	private Address address;
	
	@NumberFormat(pattern="#,###.##")
	private Integer price;
	
	/*//日期格式化,轉換為yyyy-MM-dd格式
	@DateTimeFormat(pattern="yyyy-MM-dd")
	@Past //規定必須是一個過去的時間
	private Date brith;*/
	
	public Book(){}

	
	
	public Book(String bookName, String author, String authorEmail,
			Address address, Integer price) {
		super();
		this.bookName = bookName;
		this.author = author;
		this.authorEmail = authorEmail;
		this.address = address;
		this.price = price;
	}



	public String getBookName() {
		return bookName;
	}

	public void setBookName(String bookName) {
		this.bookName = bookName;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getAuthorEmail() {
		return authorEmail;
	}

	public void setAuthorEmail(String authorEmail) {
		this.authorEmail = authorEmail;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public Integer getPrice() {
		return price;
	}

	public void setPrice(Integer price) {
		this.price = price;
	}

	/*public Date getBrith() {
		return brith;
	}

	public void setBrith(Date brith) {
		this.brith = brith;
	}*/

	@Override
	public String toString() {
		return "Book [bookName=" + bookName + ", author=" + author
				+ ", authorEmail=" + authorEmail + ", address=" + address
				+ ", price=" + price  + "]";
	};
	
	
}

BookController.java(控制器,轉發請求)

package com.luyi.controller;


import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.luyi.dao.Book;

@Controller
public class BookController {
	
	@RequestMapping(value="/toAddBookPage")
	public String toAddBookPage(Model model){
		Book book1 = new Book();
		book1.setBookName("西遊記");
		model.addAttribute("book", book1);
		return "addBook";
	}
	
	@RequestMapping(value="/addBook", method=RequestMethod.POST)
	//使用@Valid註解告訴springMVC這個javaBean需要校驗
	//這個BindingResult就封裝了bean的校驗結果
	public String addBook(@Valid Book book, BindingResult result){
		
		if(result.hasErrors()){
			System.out.println("資料輸入錯誤");
		}
		System.out.println("我要儲存的圖書" + book);
		return "success";	
	}
}

addBook.jsp(新增圖書頁面):

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form:form action="addBook" modelAttribute="book" method="POST">
	書名:<form:input path="bookName"/>
	<!-- 錯誤回顯 -->
	<form:errors path="bookName" /><br>
	作者:<form:input path="author"/><form:errors path="author" /><br>
	作者郵箱:<form:input path="authorEmail"/><form:errors path="authorEmail" /><br>
	書價:<form:input path="price"/><form:errors path="price" /><br>
	<hr>
	省份:<form:input path="address.provice"/><br>
	城市:<form:input path="address.city"/><br>
	<input type="submit" value="提交資料">
</form:form>
</body>
</html>

springMVC.xml(spring配置檔案):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		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.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	
	<!-- 匯入jstlView包,可以支援便捷的國際化功能,還有一種就是直接設定viewClass屬性值為JstlView也行 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>

	</bean>
	
	
	<!-- 開啟MVC註解驅動模式 ,如果沒有這一段程式碼,
	則除了toLoginPage請求以外,其他的請求都會請求不到資料了-->
	<mvc:default-servlet-handler/>
	<mvc:annotation-driven ></mvc:annotation-driven>
	
	
</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 配置一個字元編碼的Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<!-- encoding:指定解決POST請求亂碼 -->
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<!-- forceEncoding:解決了響應亂碼問題 -->
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 使得其支援REST風格 -->
	<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>
	
	
</web-app>

注意:如果是普通表單,不能通過form:errors標籤獲取錯誤資訊時,可以把錯誤資訊放在域中給頁面獲取

自定義固定訊息:在註解上面加上message屬性即可,如@NotEmpty(message="不能為空")

自定義國際化錯誤訊息:

每個屬性在資料繫結和資料校驗發生錯誤時,都會生成一個對應的 FieldError 物件。當一個屬性校驗失敗後,校驗框架會為該屬性生成 4 個訊息程式碼,這些程式碼以校驗註解類名為字首,結合 modleAttribute、屬性名及屬性型別名生成多個對應的訊息程式碼,這4個訊息程式碼就是我們國際化資原始檔的鍵,4個訊息程式碼如下:

  • Email.book.email:如果是隱含模型中book物件中的email屬性欄位傳送了@Email校驗錯誤,就會生成Email.book.email
  • Email.email:所有email屬性只要發生了@Email錯誤,就會生成這個錯誤
  • Email.java.lang.String:只要是String型別傳送了@Email錯誤,就會生成這個錯誤
  • Email:只要發生了@Email校驗錯誤就會生成這個錯誤

4個訊息程式碼作為國際化資原始檔的鍵的應用:

errors_en_US.properties:

Email.email=email incorrect!
NotEmpty=must not empty!
Length.java.lang.String=length incorrect ,must between {2} and {1}!

errors_zh_CN.properties:

Email.email=\u90AE\u7BB1\u683C\u5F0F\u4E0D\u6B63\u786E
NotEmpty=\u4E0D\u80FD\u4E3A\u7A7A
Length.java.lang.String=\u957F\u5EA6\u6CA1\u6709\u5728\u6307\u5B9A\u8303\u56F4\u5185\uFF0\uFF0C\u53D6\u503C\u8303\u56F4\u5FC5\u987B\u5728{2}\u5230{1}\u4E4B\u95F4

注意:{0},{1}等等是資原始檔的佔位符,{0}永遠代表的就是當前屬性名,後面的順序1,2代表的數就要根據它的這個註解的屬性名的順序決定的,如min小於max,因此{1}代表的就是max,{2}代表的就是min

JSR303:是java為Bean資料合法性校驗提供的標準框架,它已經包含在JavaEE6.0中,JSR303通過在Bean屬性上標註類似於 @NotNull、@Max 等標準的註解指定校驗規則,並通過標準的驗證介面對 Bean 進行驗證,我們的Hibernate Validator就實現了這樣的JSR303規範

所有註解以及這些註解的功能:

Hibernate Validator 擴充套件註解:Hibernate Validator 是 JSR 303 的一個參考實現,除支援所有標準的校驗註解外,它還支援以下的擴充套件註解:

  • @Email:被註釋的元素必須是電子郵箱地址
  • @Length:被註釋的字串的大小必須在指定的範圍內
  • NotEmpty:被註釋的字串必須非空
  • @Range:被註釋的元素必須在合適的範圍內

SpringMVC資料校驗:

  • Spring 4.0 擁有自己獨立的資料校驗框架,同時支援 JSR 303 標準的校驗框架。
  • Spring 在進行資料繫結時,可同時呼叫校驗框架完成資料校驗工作。在 Spring MVC 中,可直接通過註解驅動的方式進行資料校驗
  • Spring 的 LocalValidatorFactroyBean 既實現了 Spring 的 Validator 介面,也實現了 JSR 303 的 Validator 介面。只要在 Spring 容器中定義了一個 LocalValidatorFactoryBean,即可將其注入到需要資料校驗的 Bean 中。
  • Spring 本身並沒有提供 JSR303 的實現,所以必須將 JSR303 的實現者的 jar 包放到類路徑下。
  • mvc:annotation-driven/ 會預設裝配好一個 LocalValidatorFactoryBean,通過在處理方法的入參上標註 @valid 註解即可讓 Spring MVC 在完成資料繫結後執行資料校驗的工作
  • 在已經標註了 JSR303 註解的表單/命令物件前標註一個 @Valid,Spring MVC 框架在將請求引數繫結到該入參物件後,就會呼叫校驗框架根據註解宣告的校驗規則實施校驗
  • Spring MVC 是通過對處理方法簽名的規約來儲存校驗結果的:前一個表單/命令物件的校驗結果儲存到隨後的入參中,這個儲存校驗結果的入參必須是 BindingResult 或 Errors 型別,這兩個類都位於 org.springframework.validation 包中
  • 需校驗的 Bean 物件和其繫結結果物件或錯誤物件時成對出現的,它們之間不允許宣告其他的入參
  • Errors 介面提供了獲取錯誤資訊的方法,如 getErrorCount() 或 getFieldErrors(String field)
  • BindingResult 擴充套件了 Errors 介面

7.SpringMVC支援ajax

原生javaWeb實現ajax:

  • 匯入GSON包
  • 返回的資料用GSON轉成json
  • 再把得到的json資料寫出去

SpringMVC-ajax:

  • 導包

    • jackson-annotations-2.1.5.jar
    • jackson-core-2.1.5.jar
    • jackson-databind-2.1.5.jar
  • 寫配置

程式碼示例:

@Controller
public class AjaxTestController {
	/**
	 * @ResponseBody將返回的資料放在響應體中,
	 * 如果是物件,jackson包自動將物件轉為json格式
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/ajaxGetAll")
	public Book ajaxGetAll(){
		Book book = new Book();
		book.setBookName("西遊記");
		book.setAuthor("羅貫中");
		return book;
		//這裡也可以寫一些資料出去,如return "<h1>成功了</h1>"
	}
}
  • 測試

除了可以通過@ResponseBody把資料放在響應體裡以外,我們還可以通過@RequestBody獲取請求體

程式碼實現如下:

index.jsp頁面:

<form action="ajaxGetRequest" method="POST">
	<input name="username" value="luyi"/>
	<input name="password" value="123"/>
	<input type="submit" value="提交資料" />
</form>

處理請求的方法:

@RequestMapping("/ajaxGetRequest")
	public String ajaxGetRequest(@RequestBody String body){
		System.out.println("請求體 " + body);
		return "success";
	}

除此之外,如果我們的引數位置寫的是HttpEntity 物件引數的話,那我們還可以獲取到所有的請求頭資訊

除了以上這些,還有一個更厲害的東西,那就是ResponseEntity物件,它可以封裝響應體,響應頭,響應狀態等資訊

@ResponseBody
	@RequestMapping("/haha")
	public ResponseEntity<String> haha(){

		String body = "<h1>Success</h1>";
		MultiValueMap<String, String> headers = new HttpHeaders();
		headers.add("Set-Cookie", "usernmae=hahaha");
		return new ResponseEntity<String>(body, headers, HttpStatus.OK);
	}

8.springMVC的檔案下載與上傳

檔案的下載

1.得到要下載的檔案的流

2.將要下載的檔案流返回

程式碼實現:

/**
 * 處理檔案下載請求
 * @param request
 * @return
 * @throws IOException
 */
@RequestMapping("/download")
	public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException{
		//得到要下載的檔案的路徑
		//找到要下載的檔案的真實路徑
		ServletContext context = request.getServletContext();
		String realPath = context.getRealPath("/WEB-INF/page/success.jsp");
		FileInputStream fis = new FileInputStream(realPath);
		byte[] tmp = new byte[fis.available()];
		fis.read(tmp);
		fis.close();
		
		//將要下載的檔案流返回
		HttpHeaders headers = new HttpHeaders();
		headers.set("Content-Disposition", "attachment;filename=" + "success.jsp");
		return new ResponseEntity<byte[]>(tmp, headers, HttpStatus.OK);
	}

檔案上傳的步驟

1.檔案上傳表單準備:enctype="multipart/form-data"

2.匯入fileupload包:

  • commons-fileupload-1.2.1jar
  • commons-io-2.0.jar

3.在springMVC配置檔案中,編寫一個配置,配置檔案上傳解析器(MutipartResolver)

4.檔案上傳請求處理,在處理方法上寫上:@RequestParam("headerimg")MultipartFile file,封裝當前檔案的資訊,可以直接儲存,通過file.transferTo方法儲存

程式碼示例:

web.xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 配置一個字元編碼的Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<!-- encoding:指定解決POST請求亂碼 -->
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<!-- forceEncoding:解決了響應亂碼問題 -->
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 使得其支援REST風格 -->
	<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>
	
	
</web-app>

SpringMVC.xml(SpringMVC配置檔案):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		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.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	
	<!-- 匯入jstlView包,可以支援便捷的國際化功能,還有一種就是直接設定viewClass屬性值為JstlView也行 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>	
	
	<!-- 開啟MVC註解驅動模式 ,這是springMVC配置檔案的標配-->
	<mvc:default-servlet-handler/>
	<mvc:annotation-driven ></mvc:annotation-driven>
	
	<!-- 配置檔案上傳解析器 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- 設定檔案上傳最大的大小 -->
		<property name="maxUploadSize" value="#{1024*1024*20}"></property>
		<!-- 設定預設的編碼 -->
		<property name="defaultEncoding" value="utf-8"></property>
		<!--  -->
	</bean>
</beans>

index.jsp(首頁):

...
<body>
	${msg} 
	<form action="${ctp}/upload" method="POST" enctype="multipart/form-data">
		使用者頭像:<input type="file" name="headerimg"/><br>
		使用者名稱:<input type="text" name="username"/>
		<input type="submit" value="提交資料">
	</form>
</body>
...

FileUploadController(轉發請求):

package com.luyi.controller;

import java.io.File;
import java.io.IOException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class FileUploadController {
	
	@RequestMapping("/upload")
	public String upload(@RequestParam(value="usernmae", required=false)String username, 
			@RequestParam(value="headerimg")MultipartFile file,
			Model model){
		
		System.out.println("--上傳的檔案的資訊--");
		//獲取的是檔案項的name值(input name="headeriimg")
		System.out.println("input的nam屬性的值為" + file.getName());
		//獲取的是檔案的名字
		System.out.println("檔案的名字" + file.getOriginalFilename());
		
		//檔案儲存
		try {
			file.transferTo(new File("D:\\" + file.getOriginalFilename()));
			model.addAttribute("msg", "檔案上傳成功");
		}catch (Exception e) {
			model.addAttribute("msg", "檔案上傳失敗了!" + e.getMessage());
		}
		return "forward:/index.jsp";
	}
}

注意:如果是多檔案上傳,直接使用@RequestParam("headerimg")MultipartFile[] file封裝當前檔案們的資訊

9.SpringMVC的攔截器

概述:SpringMVC提供了攔截器機制,允許執行目標方法之前進行一些攔截工作,或者目標方法執行之後進行一些其他處理;可以說是JavaWeb中過濾器的加強版。SpringMVC的攔截器是一個介面(HandlerInterceptor),這個介面有三個方法:

  • boolean preHandle(HttpServletRequest, HttpServletResponse, Object):在目標方法執行之前呼叫,返回boolean;return true,則chain.doFilter()方法放行;return false,則chain.doFilter()方法不放行
  • void postHandle(HttpServletRequest, HttpServletResponse,Object, ModelAndView):在目標方法執行之後呼叫
  • void afterCompletion(HttpServletRequest, HttpServletResponse,Object,Exception):在請求整個完成之後,來到目標頁面之後呼叫

實現攔截器的步驟:

  • 實現攔截器(HandlerInterceptor)的介面
  • 在springMVC配置檔案中配置攔截器
  • 測試

攔截器的執行流程:

攔截器的preHandle(只要preHandler不放行就沒有後面的流程了,只要放行了,afterCompletion就會執行)----目標方法----攔截器postHandle----頁面----攔截器的afterCompletion

測試攔截器的程式碼實現:

實現攔截器(HandlerInterceptor)的介面:

package com.luyi.controller;

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

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

public class MyFirstInterceptor implements HandlerInterceptor {

	/**
	 * 目標方法執行之前呼叫
	 */
	@Override
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2) throws Exception {
		System.out.println("preHandle方法執行");
		return true;
	}
	
	/**
	 * 目標方法執行之後呼叫
	 */
	@Override
	public void afterCompletion(HttpServletRequest arg0,
			HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		System.out.println("afterCompletion方法執行");
		
	}

	/**
	 * 資源響應之後呼叫
	 */
	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
			Object arg2, ModelAndView arg3) throws Exception {
		System.out.println("postHandle方法執行");
	}
	
}

在springMVC配置檔案中配置攔截器:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		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.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	
	<!-- 匯入jstlView包,可以支援便捷的國際化功能,還有一種就是直接設定viewClass屬性值為JstlView也行 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>	
	
	<!-- 開啟MVC註解驅動模式 ,這是springMVC配置檔案的標配-->
	<mvc:default-servlet-handler/>
	<mvc:annotation-driven ></mvc:annotation-driven>
	
	<!-- 測試攔截器 -->
	<mvc:interceptors>
		<!-- 配置攔截器:預設是攔截所有的請求 -->
		<!--<bean class="com.luyi.controller.MyFirstInterceptor"></bean>-->
		<mvc:interceptor>
			<!-- 只攔截test1請求 -->
			<mvc:mapping path="/test1"/>
			<bean class="com.luyi.controller.MyFirstInterceptor"></bean>
		</mvc:interceptor>
	</mvc:interceptors>
</beans>

web.xml檔案配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 配置一個字元編碼的Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<!-- encoding:指定解決POST請求亂碼 -->
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<!-- forceEncoding:解決了響應亂碼問題 -->
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 使得其支援REST風格 -->
	<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>
	
	
</web-app>

測試:

index.jsp頁面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isErrorPage="true"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<%
	pageContext.setAttribute("ctp", request.getContextPath());
%>

</head>
<body>
	<a href="${ctp}/test1">test1</a>
</body>
</html>

success.jsp頁面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
System.out.println("success...");
%>
<h1>成功跳轉頁面</h1>
</body>
</html>

轉發請求的java檔案(InterceptorTestController.java):

package com.luyi.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class InterceptorTestController {
	
	@RequestMapping("test1")
	public String test1(){
		System.out.println("test1方法被呼叫");
		return "success";
	}
}

多攔截器的執行流程

正常流程(放行流程,攔截器的流程就是過濾器的流程):

  • 哪一個攔截器配置在前面,哪個攔截器就先執行他的prehandle
  • 攔截器的preHandle是按照順序執行的
  • 攔截器的postHandle是按照逆序執行的
  • 攔截去的afterCompletion是按照逆序執行的

異常流程(不放行時的流程):哪一個攔截器不放行,則從這個攔截器之後的攔截器就不會走了,哪一個攔截器放行了,他的afterCompletion就一定會執行

過濾器與攔截器(攔截器是配置在容器中的,使用其他元件時直接通過@Autowired創建出一個元件來)

如果某些功能,需要其他元件配合完成,我們就使用攔截器,其他情況可以寫filter

10.國際化

國際化的實現原理

前面已經有講過簡單的國際化的實現,那麼,我們的國際化是怎麼實現的呢?我們的國際化實現顯示不同語言的資訊是根據瀏覽器帶來的語言資訊決定的,可以獲取瀏覽器的區域資訊(通過Locale locale = request.getLocale()獲取),在瀏覽器傳送請求的時候,request中會攜帶一個locale的屬性,裡面標註著當前瀏覽器語言環境;在SpringMVC底層,ResourceBundleMessageSource類主要處理國際化請求,當請求傳送過來以後,該類會根據容器中配置的basename找到配置檔案中的國際化配置檔案,根據該locale值會得到配置檔案中配置的鍵值對來發送給前端使用。相反,如果沒找到的話,會採用預設方式展示。

自定義區域資訊解析器(預設的區域資訊解析器AcceptHeaderLocaleResolver)

通過自定義區域資訊解析器實現點選連結切換國際化:

配置檔案:

//SpringMVC.xml:

<?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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		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.0.xsd">
	
	<context:component-scan base-package="com.luyi"></context:component-scan>
	
	
	<!-- 匯入jstlView包,可以支援便捷的國際化功能,還有一種就是直接設定viewClass屬性值為JstlView也行 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/page/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>	
	
	<!-- 開啟MVC註解驅動模式 ,這是springMVC配置檔案的標配-->
	<mvc:default-servlet-handler/>
	<mvc:annotation-driven ></mvc:annotation-driven>
	
	<!-- 配置國際化 -->
	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basename" value="login/i18n"></property>
	</bean>
	
	<!-- 自定義區域資訊解析器 -->
	<bean id="localeResolver" class="com.luyi.controller.MyLocaleResolver">
		
	</bean>
</beans>

//web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>springMVC_01</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- 配置前端控制器 -->
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 指定springmvc配置檔案位置 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springMVC.xml</param-value>
		</init-param>
		<!-- servlet啟動載入,servlet原來是第一次訪問建立物件;
		load-on-startup:伺服器啟動的時候建立物件,值越小,越先建立物件 -->
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<!-- /*和/都是攔截所有請求;
		/*的範圍更大,還會攔截*.jsp這些請求,一旦攔截jsp頁面就不能顯示了-->
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!-- 配置一個字元編碼的Filter -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<!-- encoding:指定解決POST請求亂碼 -->
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<!-- forceEncoding:解決了響應亂碼問題 -->
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
		
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 使得其支援REST風格 -->
	<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>
	
	
</web-app>

國際化資原始檔:

//i18n_en_US.properties:

welcomeinfo=WELCOME TO LOGIN PAGE
username=USERNAME
password=PASSWORD
loginBtn=LOGIN


//i18n_zh_CN.properties:

welcomeinfo=\u6B22\u8FCE\u6765\u5230\u767B\u5F55\u9875\u9762
username=\u7528\u6237\u540D
password=\u5BC6\u7801
loginBtn=\u63D0\u4EA4

自定義的區域資訊解析器:

package com.luyi.controller;

import java.util.Locale;

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

import org.springframework.web.servlet.LocaleResolver;

public class MyLocaleResolver implements LocaleResolver {

	@Override
	public Locale resolveLocale(HttpServletRequest request) {
		Locale l = null;
		String localeStr = request.getParameter("locale");
		if(localeStr != null && !"".equals(localeStr)){
			l = new Locale(localeStr.split("_")[0], localeStr.split("_")[1]);
		}else{
			l = request.getLocale();
		}
		return l;
	}

	@Override
	public void setLocale(HttpServletRequest arg0, HttpServletResponse arg1,
			Locale arg2) {
		// TODO Auto-generated method stub

	}

}

轉發請求:

package com.luyi.controller;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class I18nTestController {
	
	@Autowired
	private MessageSource messageSource;
	@RequestMapping("/toLoginPage")
	public String toLogin(Locale locale){
		System.out.println(locale);
		String welcome = messageSource.getMessage("welcomeinfo", null, null);
		System.out.println(welcome);
		return "login";
	}
}

其他的區域資訊解析器

SessionLocaleReslover和CookieLocaleReslover等

11.異常處理

概述:所有的異常處理器嘗試解析異常,解析完成進行後續,解析失敗則嘗試用下一個解析器解析

四個異常解析器

  • ExceptionHandlerExceptionResolver:@ExceptionHandler,用於處理異常
  • ResponseStatusExceptionResolver:@ResponseStatus,用於自定義異常類
  • DefaultHandlerExceptionResolver:預設處理異常的解析器
  • SimpleMappingExceptionResolver:以配置的方式進行異常處理(這個不配置的話預設只有三個異常處理器)

異常處理-@ExceptionHandler

告訴SpringMVC這個方法專門處理這個類發生的異常:

  • 給方法上隨便寫一個Exception,用來接收發生的異常
  • 要攜帶異常資訊不能給引數位置寫Model
  • 返回ModelAndView型別即可把資料放到隱含模型裡去
  • 如果有多個@ExceptionHandler都能處理這個異常,精確優先
  • 全域性異常處理與本類異常處理同時出現時,使用本類的異常處理

程式碼示例:

//這裡可以寫多個Exception
@ExceptionHandler(value={ArithmeticException.class})
//這裡不能在引數位置寫Model
public ModelAndView handleException1(Exception exception){
	System.out.println("handleException1" + exception);
	
	ModelAndView modelAndView = new ModelAndView("error");
	modelAndView.addObject("ex", exception);
	return modelAndView;
}

全域性異常(幾種處理所有異常)的實現:必須把這個處理所有異常的類加入到ioc容器中(通過@ControllerAdvice註解加入)

程式碼實現:

package com.luyi.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class MyExceptionHandler {
		//這裡可以寫多個Exception
		@ExceptionHandler(value={ArithmeticException.class})
		//這裡不能在引數位置寫Model
		public ModelAndView handleException01(Exception exception){
			System.out.println("handleException1" + exception);
			
			ModelAndView modelAndView = new ModelAndView("error");
			modelAndView.addObject("ex", exception);
			return modelAndView;
		}
		
		//這裡可以寫多個Exception
		@ExceptionHandler(value={NullPointerException.class})
		//這裡不能在引數位置寫Model
		public ModelAndView handleException02(Exception exception){
			System.out.println("handleException1" + exception);
			
			ModelAndView modelAndView = new ModelAndView("error");
			modelAndView.addObject("ex", exception);
			return modelAndView;
		}
		
		//這裡可以寫多個Exception
		@ExceptionHandler(value={Exception.class})
		//這裡不能在引數位置寫Model
		public ModelAndView handleException03(Exception exception){
			System.out.println("handleException1" + exception);
			
			ModelAndView modelAndView = new ModelAndView("error");
			modelAndView.addObject("ex", exception);
			return modelAndView;
		}
}

@ResponseStatus標註在自定義異常上返回一個伺服器錯誤(自己選擇報錯的資訊)

程式碼實現:

package com.luyi.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(reason="使用者被拒絕", value = HttpStatus.NOT_ACCEPTABLE)
public class UserNotFoundException extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

}

DefaultHandlerExceptionResolver:

概述:預設處理異常的解析器,當沒有使用@ExceptionHandler或者沒有自定義丟擲異常(@RequestStatus)時,就走預設的異常解析器DefaultHandlerExceptionResolver

SimpleMappingExceptionResolver

概述:這個異常解析器不配置的話預設只有三個異常解析器,不會有這一個SimpleMappingExceptionResolver解析器,這個解析器需要通過配置來得到,這個解析器的優先順序是低於@ExceptionHandler和@ResponseStatus的,但是高於預設的解析器

程式碼演示(配置檔案中配置):

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<!-- key:異常全類名,value:要去的頁面檢視名 -->
			<prop key="java.lang.ArithmeticException">error</prop>
		</props>
	</property>
	<!-- 指定錯誤資訊取出時預設的值,如果這裡不配置,預設在隱含模型中的key為exception -->
	<property name="exceptionAttribute" value="ex"></property>
</bean>

12.SpringMVC的執行流程

1.所有請求,前端控制器(DispatcherServlet)收到請求,呼叫doDispatch進行處理

2.根據HandlerMapping中儲存的請求對映資訊找到這個請求,處理當前請求的,處理器執行鏈(包含攔截器)

3.根據當前處理器找到它的HandlerAndView(介面卡)

4.攔截器的preHandle先執行

5.介面卡執行目標方法,並返回ModelAndView

  • ModelAttribute註解標註的方法提前執行
  • 執行目標方法的時候(確定目標方法的引數)
    • 有註解
    • 沒註解
      • 看是否有Model,Map,以及其他的
      • 如果是自定義型別
        • 從隱含模型中看有沒有這個引數,如果有,從隱含模型中拿
        • 如果沒有。再看是否是SessionAttributes標註的屬性。如果是,從Session中拿,如果拿不到會拋異常
        • 都不是,就利用反射建立物件

6.攔截器的postHandle執行

7.處理結果(頁面渲染流程):

  • 如果有異常使用異常解析器處理異常,處理完後還會返回ModelAndView

  • 呼叫render進行頁面渲染

    • 檢視解析器根據檢視名得到檢視物件
    • 檢視物件呼叫render方法
  • 執行攔截器的afterCompletion

13.Spring與SpringMVC的整合

SpringMVC和Spring整合的目的:分工明確

SpringMVC的配置檔案就來配置和網站轉發邏輯以及網站功能有關的功能(檢視解析器,檔案上傳解析器,支援ajax等)

Spring的配置檔案來配置和業務有關的東西(事務控制,資料來源等)

SpringMVC和Spring整合的方法:

在web.xml檔案中加入這兩個配置檔案,就會有兩個容器,這兩個容器再分別掃描不同的元件,即可達到與一個容器一樣的功能。但是,我們的spring容器和springMVC容器同時存在時,其實是有子父關係的,springMVC是Spring容器的子類,而子類是可以訪問父類的元件的,但是父類是訪問不了子類的元件的,這就會導致在Spring容器中,想要自動裝配SpringMVC容器中的元件時會報錯

程式碼實現:

在spring配置檔案中掃描除了Controller註解和ControllerAdvice註解之外的所有添加了註解的類:

<context:component-scan base-package="com.luyi">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>

在springMVC配置檔案中掃描添加了Controller註解和ControllerAdvice註解的類:

<context:component-scan base-package="com.luyi" use-default-filters="false">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>