Spring 為表現層提供的基于 MVC 設計理念的 Web 框架。
Spring MVC 通過一套 MVC 注解,讓 POJO 成為處理器請求的控制器,而無需實現任何接口。
支持 REST 風格的 url 請求。
采用松散耦合可插拔組件結構,比其他 MVC 框架更具有擴展性和靈活性。
首先,是要導入 jar 包
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
application.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.xsd">
<context:component-scan base-package="com.kernel.spring.web"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
HelloController.java
package com.kernel.spring.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "/success";
}
}
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>SUCCESS!</h2>
</body>
</html>

在控制器的類定義及方法定義上都可以標注 @RequestMapping。
標記在類上:提供初步的映射信息,相當于 Web 應用的根目錄。
標記在方法上:提供細分的映射信息,如果類上未標注該注解,那么方法處標記的 URL 相對于 Web 應用的根目錄。
作用:DispatcherServlet 截獲請求后,就根據 @RequestMapping 提供的映射信息確定請求所對應的處理方法。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String[] value() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
package com.kernel.spring.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
// 標注該類是一個控制器組件
@Controller
public class HelloController {
// 映射請求信息
@RequestMapping("/hello")
public String hello() {
// 返回值會通過視圖解析器解析為實際的物理視圖,然后做轉發操作
// 對于 InternalResourceViewResolver,會通過 prefix + returnValue + suffix 這樣的方式得到實際的物理視圖
return "/success";
}
}
該注解除了可以使用請求 URL 映射請求外,還可以使用請求方法、請求參數和請求頭映射請求。
結果使用可以讓請求更加精確化
@Controller
public class TestController {
@RequestMapping(value = "/testMethod", method = RequestMethod.POST)
public String testMethod(){
System.out.println("testMethod...");
return "/success";
}
}
如果以 get 方式請求,會報 405 請求不支持錯誤。
@Controller
public class TestController {
@RequestMapping(value="/testParamsAndHeaders",
params= {"username","age!=10"},
headers = { "Accept-Language=en-US,zh;q=0.9" })
public String testParamsAndHeaders(){
System.out.println("testParamsAndHeaders...");
return "/success";
}
}
測試
http://localhost:8080/testParamsAndHeaders 不可以訪問
http://localhost:8080/testParamsAndHeaders?username 可以訪問
http://localhost:8080/testParamsAndHeaders?username&age 可以訪問
http://localhost:8080/testParamsAndHeaders?username=kernel&age=10 不可以訪問
結論:必須至少攜帶一個參數,參數可以傳空,但是 age 不能等于10。
@PathVariable 可以將請求中的參數,傳遞給處理請求方法的入參中。
@Controller
public class TestController {
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id){
System.out.println("testPathVariable...id" + id);
return "/success";
}
}
必須攜帶一個整數類型的參數才可以訪問。
REST:
即 Representational State Transfer。(資源)表現層狀態轉化。是目前最流行的一種互聯網軟件架構。它結構清晰、符合標準、易于理解、擴展方便,所以正得到越來越多網站的采用。
URL 風格:
| HTTP 方法 | 說明 |
|---|---|
| POST | 新增資源 |
| DELETE | 刪除資源 |
| PUT | 修改資源 |
| GET | 獲取資源 |
HiddenHttpMethodFilter:
總所周知,瀏覽器 from 表單只支持兩種請求,即 GET 和 POST,而 PUT 和 DELETE 不支持,Spring 3.0 提供了一個過濾器,將這些請求轉換為 PUT 和 DELETE 請求。
web.xml
<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>
delete.jsp
<form action="/testRESTDelete/1" method="POST">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="testRESTDelete">
</form>
源碼參考
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(this.methodParam);
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
從源碼看出來,Spring 根據請求中的 _method 參數進行轉換,并且只對 POST 進行處理, GET 請求進行處理,所有如果想轉換請求,必須要將表單提交方式設置成 POST,必須將 _method 的值設置為 DELETE 或 PUT。
Spring MVC 通過分析處理方法的簽名,HTTP 請求信息綁定到處理方法的入參中。
Spring MVC 對控制器處理方法簽名的限制是很寬松的,幾乎可以按照喜歡的方式對方法簽名。
必要時可以對方法及方法入參標注響應的注解(@PathVariable 、@RequestParam、@RequestHeader 等)。
Spring MVC 會將 HTTP 請求的信息綁定到響應的方法入參中,并根據方法的返回值做出相應的后續處理。
在處理方法入參時使用該注解可以將請求參數傳遞給方法入參。
value:參數名
required:是否必須,默認為 true,若不存在,拋出異常。
defaultValue:默認值,沒有傳遞參數的時候使用。
@Controller
public class TestController {
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(value = "id",
required = false,
defaultValue = "0") Integer id) {
System.out.println("testRequestParam...id" + id);
return "/success";
}
}
在處理方法入參時使用該注解可以將請求頭傳遞給方法入參。
value:參數名
required:是否必須,默認為 true,若不存在,拋出異常。
defaultValue:默認值,沒有傳遞參數的時候使用。
@Controller
public class TestController {
@RequestMapping("/testRequestHeader")
public String testHeader(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Connection") String connection) {
System.out.println("testRequestHeader...Accept-Encoding" + encoding);
System.out.println("testRequestHeader...Connection" + connection);
return "/success";
}
}
使用該注解可以綁定請求中的 Cookie 值。
value:參數名
required:是否必須,默認為 true,若不存在,拋出異常。
defaultValue:默認值,沒有傳遞參數的時候使用。
@Controller
public class TestController {
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue("JSESSIONID") String jsessionid) {
System.out.println("testCookieValue...JSESSIONID" + jsessionid);
return "/success";
}
}
使用 POJO 對象綁定請求參數值。
Spring MVC 會按照請求參數名和 POJO 屬性進行自動匹配,還支持級聯屬性。
TestController.java
@Controller
public class TestController {
@RequestMapping("/testPOJO")
public String testPOJO(User user) {
System.out.println("testPOJO...user" + user);
return "/success";
}
}
.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="/testPOJO" method="post">
ID:<input type="text" name="id"><br>
賬戶:<input type="text" name="username"><br>
密碼:<input type="text" name="password"><br>
郵箱:<input type="text" name="email"><br>
年齡:<input type="text" name="age"><br>
省份:<input type="text" name="address.province"><br>
城市:<input type="text" name="address.city"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
Spring MVC 的 Handler 方法可以接受哪些 ServletAPI 類型的參數?
Spring MVC 提供了以下幾種途徑輸出模型數據:
ModelAndView:處理方法返回值為 ModelAndView 時,即可以通過該對象添加模型數據。
Map、Model:入參為 org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 時,處理方法返回時,Map 中的數據會自動添加到模型中。
@SessionAttributes:將模型中的數據暫存到 HttpSession中,以便在多個請求中共享這個屬性。
@ModelAttribute:方法入參標注該注解后,入參的對象會放入到模型數據中。
控制器如果返回類型是 ModelAndView,則其既可以包含視圖信息,又可以包含模型數據信息。
添加模型:
MoelAndView addObject(String attributeName, Object attributeValue)
ModelAndView addAllObject(Map<String, ?> modelMap)
設置視圖:
void setView(View view)
void setViewName(String viewName)
@Controller
public class TestController {
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
System.out.println("testModelAndView...");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/success");
modelAndView.addObject("time",new Date());
return modelAndView;
}
}
事實上,返回的類型可以是很多種,可以是 ModelAndView 類型,也可以是 String 類型,還可以是 View 類型,還有很多。但是他們最終會被解析為 ModelAndView 類型的對象。
Spring MVC 在內部使用了一個 org.springframework.ui.Model 接口存儲模型數據。
具體使用步驟:
Spring MVC 在調用方法之前會創建一個隱含的模型對象作為模型對象的存儲容器。
如果方法的入參為 Map 或者 Model 類型,Spring MVC 會將隱含模型的引用傳遞給入參。
在方法體內,可以通過這個入參對象訪問到模型中的所有數據,也可以向模型中添加新的屬性數據。
@Controller
public class TestController {
@RequestMapping("/testMap")
public String testMap(Map<String, User> map) {
map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18,
new Address("山東", "德州")));
return "/success";
}
}
若希望在多個請求之間共用某個模型對象,可以在控制器上標志這個注解,Spring MVC 將對應的模型屬性暫存到HttpSession 中。
@SessionAttributes(types=User.class) 會將隱含模型的所有類型為 User 的屬性全部添加到會話中。
@SessionAttributes(value={“user1”, “user2”}) 將 user1、user2 添加到會話中。
源碼參考
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
String[] value() default {}; // 推薦使用
Class<?>[] types() default {}; // 作用范圍太廣
}
@Controller
@SessionAttribue("user)
public class TestController {
@RequestMapping("/testMap")
public String testMap(Map<String, User> map) {
// 如果該類有 @SessionAttribute 注解,會同時將 user 存放到 request 作用域和 session 作用域
// 否則將 user 放到 request 作用域。
map.put("user", new User(1, "kernel", "123456", "kernel@qq.com", 18,
new Address("山東", "德州")));
return "/success";
}
}
使用場景?
假如說我修改一個訂單的時候,訂單的創建時間是不允許被修改的,所以我更新時,因為時間字段沒有值,所有更新會將該字段更新為 null。
解決方法有:
隱藏域,字段多太麻煩,還有用戶可以在源代碼中看到隱藏域中的信息,不安全。
先查詢數據庫,然后一一賦值,比較麻煩。
下面使用 @ModelAttribute
在方法定義上使用該注解,會在調用目標方法之前逐個調用方法上標注該注解的的方法。
在方法的入參上標注該注解,可以從隱含對象中獲取隱含模型數據中獲取對象,再將請求參數綁定到對象中,在傳入入參。
@Controller
@SessionAttribue("user)
public class TestController {
@ModelAttribute
public void getUser(@RequestParam(value = "id", required = false) Integer id,
Map<String, User> map) {
if (id != null) {
User user = new User(1, "kernel", "123456", "kernel@qq.com", 18,
new Address("山東", "德州"));
System.out.println(user);
map.put("user", user);
}
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("testModelAttribute...user" + user);
return "/success";
}
}
原理分析:
執行 @ModelAttribute 注解所修飾的方法,將從數據庫中獲取的對象存放到 Map 集合中,key 為 user。
Spring MVC 從 map 中查找 user 對象,將表單數據封裝到與參數名稱對應的 user 對象屬性上。
SpringMVC將user對象作為參數,傳遞給目標方法。
SpringMVC 確定目標方法 POJO 類型入參的過程:
確定一個 key:若目標方法的 POJO 類型的參數木有使用 @ModelAttribute 作為修飾,則 key 為 POJO 類名第一個字母的小寫,若使用了@ModelAttribute 來修飾,,則 key 為 @ModelAttribute 注解的 value 屬性值。
在 implicitModel 中查找 key 對應的對象, 若存在,則作為入參傳入。
若 implicitModel 中不存在 key 對應的對象,則檢查當前的 Handler 是否使用 @SessionAttributes 注解修飾。
若使用了該注解,且 @SessionAttributes 注解的 value 屬性值中包含了 key,則會從 HttpSession 中來獲取 key 所對應的 value 值,若存在則直接傳入到目標方法的入參中,若不存在則將拋出異常。
若 Handler 沒有標識 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key,則會通過反射來創建 POJO 類型的參數,傳入為目標方法的參數。
SpringMVC 會把 key 和 POJO 類型的對象保存到 implicitModel 中,進而會保存到 request 中。
請求處理方法執行后,會最終返回一個 ModelAndView 對象,對于那些返回了 String、Model、Map 等的處理方法,內部也會被裝配成一個ModelAndView,它包含了邏輯名和模型對象的視圖。
通過視圖解析器得到最終的視圖對象,最終的視圖可以是 JSP、Excel、Pdf 等各種形式。
對于采取什么視圖對模型渲染,處理器并不關心,從而實現 MVC 的充分解耦。

每個視圖解析器都實現了 Ordered 接口并開放出一個 order 屬性,可以通過 order 屬性指定解析器的優先順序,order 越小優先級越高。
SpringMVC 會按視圖解析器順序的優先順序對邏輯視圖名進行解析,直到解析成功并返回視圖對象,否則將拋出 ServletException 異常。
若項目中使用了 JSTL,則 InternalResourceView 會轉換成 JstlView。
若想使用 JSTL 的 fmt 標簽,則必須配置國際化資源文件。
i8n國際化
# i18n.properties
i18n.username=Username
i18n.password=Password
# i18n_zh_CN.properties
i18n.username=用戶名
i18n.password=密碼
# i18n_en_US.properties
i18n.username=Username
i18n.password=Password
application.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.xsd">
<context:component-scan base-package="com.kernel.spring.web"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
<!-- 直接配置響應的頁面:無需經過控制器來執行結果 -->
<mvc:view-controller path="/success" view-name="success"/>
</beans>
若希望使用 Excel 展示數據列表,僅需要擴展 SpringMVC 提供的 AbstractExcelView 或 AbstractJExcelView 即可。
實現 buildExcelDocument() 方法,在方法中使用模型數據對象構建 Excel 文檔就可以了。
AbstractExcelView 基于 POI API,而 AbstractJExcelView 是基于 JExcelAPI 的。
視圖對象需要配置 IOC 容器中的一個 Bean ,使用 BeanNameViewResolver 作為視圖解析器即可。
若希望直接在瀏覽器中直接下載 Excel 文檔,則可以設置響應頭 Content-Disposition 的值為attachment;filename=xxx.xls。
application.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.xsd">
<context:component-scan base-package="com.kernel.spring.web"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"/>
</bean>
</beans>
HelloView.java
@Component
public class HelloView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.getWriter().print(new Date());
}
}
測試
@Controller
@SessionAttribue("user)
public class TestController {
@RequestMapping("/testView")
public String testView(){
System.out.println("testView...");
// 與視圖對象的 id 一致
return "helloView";
}
}
一般情況下,控制器方法返回字符串類型的值會被當成邏輯視圖名處理
如果返回的字符串中帶 forward: 或 redirect:前綴時,SpringMVC 會對他們進行特殊處理。
redirect:success.jsp:會完成一個到 success.jsp 的重定向的操作。
forward:success.jsp:會完成一個到 success.jsp 的轉發操作。
Spring MVC 將 ServletRequest 對象及目標方法的入參傳遞給 WebDataBinderFactory,以創建 DataBinder 實例對象。
DataBinder 調用裝配在上下文中的 ConversionService 組件進行數據類型轉換、數據格式化工作。
調用 Vaidator 組件對已綁定了請求消息的入參對象進行數據合法性校驗,最終生成數據綁定 BindingData 對象。
Spring MVC 抽取 BindingResult 中的入參對象和校驗錯誤對象,將它們賦給處理方法的響應入參。
Spring MVC 通過反射機制對目標處理方法進行解析,將請求消息綁定到處理方法的入參中。數據綁定的核心部件是 DataBinder,運行機制如下:

ConversionService 是 Spring 類型轉換器的核心。
可以使用 ConversionServiceFactoryBean 在 Spring IOC 容器中定義一個 ConversionService,Spring 將自動識別出 IOC 容器中的 ConversionService,并在 Bean 屬性配置及 Sprng MVC 處理方法入參綁定等場合使用它進行數據轉換。
Spring 支持的轉換器類型:
Converter<S, T>:將 S 類型對象轉為 T 類型對象
ConverterFactory:將相同系列多個 “同質” Converter 封裝在一起。如果希望將一種類型的對象轉換為另一種類型及其子類的對象。
GenericConverter:會根據源類對象及目標類對象所在的宿主類中的上下文信息進行類型轉換
StringToUserConverter.java
@Component
public class StringToUserConverter implements Converter<String, User> {
@Override
public User convert(String s) {
if (s != null){
String[] vals = s.split("-");
String username = vals[0];
String password = vals[1];
String email = vals[2];
Integer age = Integer.valueOf(vals[3]);
String province = vals[4];
String city = vals[5];
return new User(null, username, password,email,age , new Address(province, city));
}
return null;
}
}
application.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.kernel.spring.web"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean name="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"/>
</bean>
<bean id="serviceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="stringToUserConverter"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="serviceFactoryBean"/>
</beans>
測試
@Controller
@SessionAttribue("user)
public class TestController {
@RequestMapping("/testConverter")
public String testConverter(User user) {
System.out.println(user);
return "/success";
}
}
直接配置響應的頁面,無需經過控制器的處理,但是直接影響其他請求路徑失效。
找不到靜態資源需要配置,\<mvc:default-servlet-handler> 將在 SpringMVC 上下文中定義一個DefaultServletHttpRequestHandler,它會對進入 DispatcherServlet 的請求進行篩查,如果發現是沒有經過映射的請求,就將該請求交由 WEB 應用服務器默認的 Servlet 處理,如果不是靜態資源的請求,才由 DispatcherServlet 繼續處理。
配置類型轉換器時,需要指定轉換器引用。
完成 JSR303 數據驗證,也需要配置該標簽。
作用:
會自動注冊:RequestMappingHandlerMapping、RequestMappingHandlerAdapter 與ExceptionHandlerExceptionResolver 三個 bean。
還將提供以下支持:
支持使用 ConversionService 實例對表單參數進行類型轉換。
支持使用 @NumberFormat、@DateTimeFormat 注解完成數據類型的格式化。
支持使用 @Valid 注解對 JavaBean 實例進行 JSR 303 驗證。
支持使用 @RequestBody 和 @ResponseBody 注解。
由 @InitBinder 標識的方法,可以對 WebDataBinder 對象進行初始化,WebDataBinder 是 DataBinder 的子類,用于完成從表單字段到 JavaBean 的綁定。
由 @InitBinder 標識的方法不能有返回值,必須是 void 類型。
由 @InitBinder 表示的方法入參通常是 WebDataBinder。
Spring 在格式化模塊中定義了一個實現了 ConversionService 接口的 FormattingConversionService 實現類,該實現類擴展了 GenericConversionService 實現類,該類既有類型轉換的功能,又有格式化的功能。
FormattingConversionService 擁有一個 FormattingConversionServiceFactory 工廠類,后者用于在 Spring 上下文中給構造前者,FormattingConversionServiceFactory 內部注冊了:
NumberFormatAnnotationFormatterFactroy:支持對數字使用 @NumberFormat 注解。
odaDateTimeFormatAnnotationFormatterFactroy:支持對日期類型使用 @DataTimeFormat 注解。
裝配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入參綁定及模型數據輸出時使用注解驅動了。
@DataTimeFormat 可以對 Date、Calendar、Long 時間類型進行標注。
pattern:類型為字符串,指定解析/格式化字符串的模式。
iso:類型為 DateTimeFormat.ISO,指定解析/格式化字符串的 ISO 模式,包括四種:ISO.NONE(不使用,默認)、ISO.DATE(yyyy-MM-dd)、ISO.TIME(hh:mm:ss:SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
style:字符串類型,通過樣式指定日期時間的格式,由兩位字符組成,第一位表示日期的格式,第二位表示時間的格式:S(段日期/時間格式)、M(中日期/時間格式)、L(長日期/時間格式)、F(完整日期/時間格式)。
@NumberFormat,可對類似數字類型的屬性進行標注。
pattern:類型為 String,自定義樣式,如 "#,###"。
style:用于指定樣式類型,包括Style.NUMBER(正常數字類型)、Style.CURRENCY(貨幣類型)、
Style.PERCENT(百分數類型)。
使用 JSR303 驗證標準
加入 hibernate validator 驗證框架
在 application.xml 文件中配置 \<mvc:annotation-driven/>
在 Bean 屬性上增加對應的驗證注解
在目標方法的 Bean 類型的前面增加 @Valid 注解
Spring MVC 除了會將表單/命令對象的校驗結果保存到對應的 BindingResult 或 Errors 對象中外,還會將所有校驗結果保存到 “隱含模型”。
即使處理方法的簽名中沒有對應于表單/命令對象的結果入參,校驗結果也會保存在 “隱含對象” 中。
隱含模型中的所有數據最終將通過 HttpServletRequest 的屬性列表暴露給 JSP 視圖對象,因此在 JSP 中可以獲取錯誤信息。
在 JSP 頁面上可通過 <form:errors path=“userName”> 顯示錯誤消息。
<form:errors path="*"/> 顯示所有的錯誤信息
<form:errors path="lastName"/> 顯示某個表單域的錯誤信息
每個屬性在數據綁定和數據校驗發生錯誤時,都會生成一個對應的 FieldError 對象。
當一個屬性校驗失敗后,校驗框架會為該屬性生成 4 個消息代碼,這些代碼以校驗注解類名為前綴,結合 modleAttribute、屬性名及屬性類型名生成多個對應的消息代碼:
例如 User 類中的 password 屬性標注了一個 @Pattern 注解,當該屬性值不滿足 @Pattern 所定義的規則時, 就會產生以下 4 個錯誤代碼:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
當使用 Spring MVC 標簽顯示錯誤消息時, Spring MVC 會查看 WEB 上下文是否裝配了對應的國際化消息,如果沒有,則顯示默認的錯誤消息,否則使用國際化消息。
若數據類型轉換或數據格式轉換時發生錯誤,或該有的參數不存在,或調用處理方法時發生錯誤,都會在隱含模型中創建錯誤消息。
其錯誤代碼前綴說明如下:
required:必要的參數不存在。如 @RequiredParam(“param1”) 標注了一個入參,但是該參數不存在
typeMismatch:在數據綁定時,發生數據類型不匹配的問題
methodInvocation:Spring MVC 在調用處理方法時發生了錯誤
使用 JSTL
application.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.kernel"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
<mvc:view-controller path="/i18n1" view-name="i18n1"/>
<mvc:view-controller path="/i18n2" view-name="i18n2"/>
<mvc:annotation-driven/>
</beans>
i18n1.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<fmt:message key="i18n.username"/>
<a href="i18n2">i18n2</a>
</body>
</html>
i18n2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<fmt:message key="i18n.password"/>
<a href="i18n1">i18n1</a>
</body>
</html>
@Controller
public class TestController {
@Autowired
private ResourceBundleMessageSource messageSource;
@RequestMapping("/i18n")
public String testi18n(Locale locale){
System.out.println(locale);
String userName = messageSource.getMessage("i18n.username", null, locale);
System.out.println("i18n.username="+userName);
return "i18n";
}
}
<?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.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.kernel"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
<!-- 配置SessionLocaleResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
<!-- 配置LocaleChangeInterceptor攔截器 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
<mvc:view-controller path="/i18n1" view-name="i18n1"/>
<mvc:view-controller path="/i18n2" view-name="i18n2"/>
<mvc:annotation-driven/>
</beans>
測試:
http://localhost:8080/i18n?locale=zh_CN
http://localhost:8080/i18n?locale=en_US
Spring MVC 為文件上傳提供了直接的支持,這種支持是通過即插即用的 MultipartResolver 實現的。
Spring 用 Jakarta Commons FileUpload 技術實現了一個 MultipartResolver 實現類:
CommonsMultipartResovler :
Spring MVC 上下文中默認沒有裝配 MultipartResovler,因此默認情況下不能處理文件的上傳工作,如果想使用 Spring 的文件上傳功能,需現在上下文中配置 MultipartResolver。
application.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.kernel"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置文件上傳解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="5242880"/>
</bean>
<mvc:annotation-driven/>
</beans>
upload.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="testUpload" method="post" enctype="multipart/form-data">
文件: <input type="file" name="file"/><br><br>
描述: <input type="text" name="desc"/><br><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
測試
@Controller
public class TestController {
@Autowired
private ResourceBundleMessageSource messageSource;
private static void writeToLocal(String destination, InputStream input)
throws IOException {
int index;
byte[] bytes = new byte[1024];
FileOutputStream downloadFile = new FileOutputStream(destination);
while ((index = input.read(bytes)) != -1) {
downloadFile.write(bytes, 0, index);
downloadFile.flush();
}
downloadFile.close();
input.close();
}
@RequestMapping(value = "/testUpload", method = RequestMethod.POST)
public String testUpload(@RequestParam(value = "desc",required = false) String desc,
@RequestParam("file")MultipartFile multipartFile) throws IOException {
System.out.println("desc " + desc);
System.out.println("OriginalFilename" + multipartFile.getOriginalFilename());
InputStream inputStream = multipartFile.getInputStream();
System.out.println("available " + inputStream.available());
System.out.println("inputStream " + inputStream);
writeToLocal(multipartFile.getOriginalFilename(), inputStream);
return "success";
}
}
Spring MVC 可以使用攔截器對請求進行攔截處理,用戶可以自定義攔截器來實現特定功能,自定義攔截器必須實現 HandlerInterceptor 接口。
preHandle():這個方法在業務處理器處理請求之前被調用,在該方法中對用戶請求 request 進行處理。如果程序員決定該攔截器對請求進行攔截處理后還要調用其他的攔截器,或者是業務處理器去進行處理,則返回true;如果程序員決定不需要再調用其他的組件去處理請求,則返回false。
postHandle():這個方法在業務處理器處理完請求后,但是DispatcherServlet 向客戶端返回響應前被調用,在該方法中對用戶請求request進行處理。
afterCompletion():這個方法在 DispatcherServlet 完全處理完請求后被調用,可以在該方法中進行一些資源清理的操作。
<?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.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.kernel"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:interceptors>
<bean class="com.kernel.interceptor.FirstHandlerInterceptor"/>
<bean class="com.kernel.interceptor.SecondHandlerInterceptor"/>
</mvc:interceptors>
<mvc:annotation-driven/>
</beans>
按照攔截器的配置的順序依次調用每個攔截器的 preHandle 方法,當請求業務處理器執行完畢后,依次對倒敘對每個攔截器放行,執行 postHandle 方法,當請求完全處理完畢后,然后依次倒敘執行每個攔截器的 afterCompletion 方法。
Spring MVC 通過 HandlerExceptionResolver 處理程序的異常,包括 Handler 映射、數據綁定及目標方法執行時發生的異常。
@ExceptionHandler
可以通過 @ExceptionHandler(value = {java.lang.RuntimeException.class}) 的方式捕捉一個異常,如果捕捉成功,自動執行標志該注解的方法。
@ExceptionHandler(value = {java.lang.RuntimeException.class})
public ModelAndView handlerException2(Exception ex) {
ModelAndView mv = new ModelAndView("error");
System.out.println("出現異常啦!" + ex);
mv.addObject("exception", ex);
return mv;
}
如何將異常對象從控制器攜帶給頁面
可以通過 ModelAndView 對象將異常對象添加。
異常對象捕捉的優先級
Spring MVC 是有優先級的,他會執行離捕捉異常離發生異常最近的那個方法。
@ControllerAdvice
該注解是定義在類級別上的,在類上標注了該注解后,所有控制器上發生了注解之后都會通過這個類的方法處理。
主要處理 Handler 中用 @ExceptionHandler 注解定義的方法。
@ExceptionHandler 注解定義的方法優先級問題:
例如發生的是NullPointerException,但是聲明的異常有 RuntimeException 和 Exception,此候會根據異常的最近繼承關系找到繼承深度最淺的那個 @ExceptionHandler 注解方法,即標記了 RuntimeException 的方法
ExceptionHandlerMethodResolver 內部若找不到 @ExceptionHandler 注解的話,會找 @ControllerAdvice 中的@ExceptionHandler 注解方法。
在類上標注 @ResponseStatus 注解后,可以自定義狀態碼及提示信息。
@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用戶名和密碼不匹配")
對一些特殊的異常進行處理,比如:
NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。
如果希望對所有異常進行統一處理,可以使用 SimpleMappingExceptionResolver,它將異常類名映射為視圖名,即發生異常時使用對應的視圖報告異常。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
</bean>
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。