第四章 Spring MVC
第一節 Spring MVC 簡介
1. Spring MVC
SpringMVC是一個Java 開源框架, 是Spring Framework生態中的一個獨立模塊,它基于 Spring 實現了Web MVC(數據、業務與展現)設計模式的請求驅動類型的輕量級Web框架,為簡化日常開發,提供了很大便利。
2. Spring MVC 核心組件
-
DispatcherServlet 前置控制器
負責接收請求、分發請求
-
Handler 處理器
處理器包括了攔截器、控制器中的方法等,主要負責處理請求
-
HandlerMapping 處理器映射器
解析配置文件、掃描注解,將請求與處理器進行匹配
-
HandlerAdpter 處理器適配器
根據請求來找到匹配的處理器,這個過程稱為適配
-
ViewResolver 視圖解析器
處理器執行后得到的結果可能是一個視圖,但這個視圖屬于邏輯視圖(頁面中存在邏輯代碼,比如循環、判斷),需要使用視圖解器行處理,這個過程稱為渲染視圖
第二節 Spring MVC 發展演變
<!--低版本-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>4.3.9.RELEASE</version>
</dependency>
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope>
</dependency>
1. Bean的名字或ID匹配URL請求
1.1 web.xml 配置
<servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><!--配置Servlet初始化參數--><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.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>
1.2 spring-mvc.xml 配置
<!--視圖解析器:在控制器返回視圖的時候生效-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--視圖資源的前綴--><property name="prefix" value="/" /><!--視圖資源的后綴--><property name="suffix" value=".jsp" />
</bean>
<!--處理器映射的方式:使用bean的名字或者id的值來與請求匹配-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
1.3 編寫控制器
public class UserController extends AbstractController {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {//這里使用配置的視圖解析器進行解析 user => / + user + .jsp => /user.jspreturn new ModelAndView("user");}
}
1.4 配置控制器
<!--通過id值匹配請求的URL-->
<bean id="/view" class="com.qf.spring.mvc.controller.UserController" />
思考:按照這種匹配請求的方式,每一個請求需要一個控制器與之對應,這與使用Servlet開發一樣,會編寫大量的控制器,導致開發效率極為低下,如何解決?
Spring 提供了方法名來匹配請求來解決這個問題
2. Bean的方法名匹配請求
2.1 方法名解析器
Spring 提供了控制器內的方法名的解析器 InternalPathMethodNameResolver,該解析器作用就是將方法名作為匹配URL請求的依據,與控制器關聯起來
2.2 多操作控制器
Spring 提供了 MultiActionController 控制器類,供其他控制器類繼承,在其子類中,開發者可以編寫多個處理請求的方法,然后使用方法名解析器去匹配請求
2.3 編寫控制器
public class UserMultiController extends MultiActionController {//這個方法就匹配 /login 請求//請求格式必須是 //ModelAndView 方法名(HttpServletRequest req, HttpServletResponse resp){}public ModelAndView login(HttpServletRequest req, HttpServletResponse resp){return new ModelAndView("login");}//這個方法就匹配 /register 請求public ModelAndView register(HttpServletRequest req, HttpServletResponse resp){return new ModelAndView("register");}
}
2.4 spring-mvc.xml 配置
<!--方法名解析器-->
<bean id="methodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver" />
<!-- /login 請求使用該bean對象處理-->
<bean id="/login" class="com.qf.spring.mvc.controller.UserMultiController"><property name="methodNameResolver" ref="methodNameResolver" />
</bean>
<!-- /register 請求使用該bean對象處理-->
<bean id="/register" class="com.qf.spring.mvc.controller.UserMultiController"><property name="methodNameResolver" ref="methodNameResolver" />
</bean>
思考:按照這種匹配請求的方式,如果一個控制器要處理多個請求,那么就會導致配置信息繁多的問題,后期難以維護,如何解決?
Spring 提供了 SimpleUrlHandlerMapping 映射器, 該映射器支持一個控制器與多個請求匹配的同時也解決了配置信息繁多的問題。
3. 簡單URL處理器映射
使用SimpleUrlHandlerMapping只需要修改 spring-mvc.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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!--視圖解析器:在控制器返回視圖的時候生效--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--視圖資源的前綴--><property name="prefix" value="/" /><!--視圖資源的后綴--><property name="suffix" value=".jsp" /></bean><!--處理器映射的方式:使用bean的名字或者id的值來與請求匹配-->
<!-- <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>--><!--通過id值匹配請求的URL-->
<!-- <bean id="/view" class="com.qf.spring.mvc.controller.UserController" />--><!--方法名解析器-->
<!-- <bean id="methodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver" />--><!-- /login 請求使用該bean對象處理-->
<!-- <bean id="/login" class="com.qf.spring.mvc.controller.UserMultiController">-->
<!-- <property name="methodNameResolver" ref="methodNameResolver" />-->
<!-- </bean>--><!-- /register 請求使用該bean對象處理-->
<!-- <bean id="/register" class="com.qf.spring.mvc.controller.UserMultiController">-->
<!-- <property name="methodNameResolver" ref="methodNameResolver" />-->
<!-- </bean>--><bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><props><prop key="/view">userController</prop><prop key="/user/*">userMultiController</prop></props></property></bean><bean id="userController" class="com.qf.spring.mvc.controller.UserController" /><bean id="userMultiController" class="com.qf.spring.mvc.controller.UserMultiController" />
</beans>
思考:隨著項目開發的推進,開發的業務功能越來越多,控制器的數量也會伴隨著增加,請求的匹配同時也會增加,同樣會造成后期難以維護的問題,如何解決呢?
Spring 提供了 DefaultAnnotationHandlerMapping 映射器,支持使用注解來匹配請求,這樣就解決了請求匹配導致配置信息繁多的問題,同時還提升了開發效率。
4. 注解匹配請求
4.1 編寫控制器
@Controller
public class UserAnnotationController {@RequestMapping(value = "/login", method = RequestMethod.GET)public String login(){return "login";}@RequestMapping(value = "/register", method = RequestMethod.GET)public String register(){return "register";}
}
4.2 spring-mvc.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"><!--視圖解析器:在控制器返回視圖的時候生效--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--視圖資源的前綴--><property name="prefix" value="/" /><!--視圖資源的后綴--><property name="suffix" value=".jsp" /></bean><!--處理器映射的方式:使用bean的名字或者id的值來與請求匹配-->
<!-- <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>--><!--通過id值匹配請求的URL-->
<!-- <bean id="/view" class="com.qf.spring.mvc.controller.UserController" />--><!--方法名解析器-->
<!-- <bean id="methodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver" />--><!-- /login 請求使用該bean對象處理-->
<!-- <bean id="/login" class="com.qf.spring.mvc.controller.UserMultiController">-->
<!-- <property name="methodNameResolver" ref="methodNameResolver" />-->
<!-- </bean>--><!-- /register 請求使用該bean對象處理-->
<!-- <bean id="/register" class="com.qf.spring.mvc.controller.UserMultiController">-->
<!-- <property name="methodNameResolver" ref="methodNameResolver" />-->
<!-- </bean>--><!--<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><props><prop key="/view">userController</prop><prop key="/login">userMultiController</prop><prop key="/register">userMultiController</prop></props></property></bean><bean id="userController" class="com.qf.spring.mvc.controller.UserController" /><bean id="userMultiController" class="com.qf.spring.mvc.controller.UserMultiController" />--><!--類上的注解處理器--><bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" /><!--方法上的注解處理器--><bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /><!--掃描包,使得該包下類以及類中定義的方法上所使用的注解生效--><context:component-scan base-package="com.qf.spring.mvc.controller" />
</beans>
5. 較新的版本配置
<?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 https://www.springframework.org/schema/mvc/spring-mvc.xsd"><!--視圖解析器:在控制器返回視圖的時候生效--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--視圖資源的前綴--><property name="prefix" value="/" /><!--視圖資源的后綴--><property name="suffix" value=".jsp" /></bean><!--較新的版本使用該標簽開啟注解支持--><mvc:annotation-driven /><!--掃描包,使得該包下類以及類中定義的方法上所使用的注解生效--><context:component-scan base-package="com.qf.spring.mvc.controller" />
</beans>
第三節 Spring MVC 常用注解
1. @Controller
該注解是一個控制器的標識
@Controller
public class UserController{}
2. @RequestMapping
該注解用于匹配請求
@Controller
@RequestMapping("/user")
public class UserController{@RequestMapping(value="/login", method=RequestMethod.POST)public int login(){return 1;}
}
3. @RequestBody
該注解只能應用在方法的參數上,用于從請求體中獲取數據并注入至參數中
@Controller
@RequestMapping("/user")
public class UserController{@RequestMapping(value="/login", method=RequestMethod.POST)public int login(@RequestBody User user){return 1;}
}
4. @ResponseBody
該注解用于向頁面傳遞數據
@Controller
@RequestMapping("/user")
public class UserController{@RequestMapping(value="/login", method=RequestMethod.POST)@ResponseBodypublic int login(@RequestBody User user){return 1;}
}
5. @RequestParam
該注解只能應用在方法的參數上,用于從請求頭中獲取數據并注入至參數中
@Controller
@RequestMapping("/user")
public class UserController{@RequestMapping(value="/search", method=RequestMethod.GET)@ResponseBodypublic List<User> searchUsers(@RequestParam(value="name") String name){return new ArrayList<>();}
}
6. @PathVariable
該注解只能應用在方法的參數上,用于從請求路徑中獲取數據并注入至參數中
@Controller
@RequestMapping("/user")
public class UserController{// /user/admin@RequestMapping(value="/{username}", method=RequestMethod.GET)@ResponseBodypublic User queryUser(@PathVariable("username") String username){return new User();}
}
7. @SessionAttributes[不重要]
該注解只能使用在類定義上,用于從將輸入放入 session 中
@SessionAttributes(types=User.class) //會將model中所有類型為 User的屬性添加到會話中。
@SessionAttributes(value={“user1”, “user2”}) //會將model中屬性名為user1和user2的屬性添加到會話中。
@SessionAttributes(types={User.class, Dept.class}) //會將model中所有類型為 User和Dept的屬性添加到會話中。
@SessionAttributes(value={“user1”,“user2”},types={Dept.class}) //會將model中屬性名為user1和user2以及類型為Dept的屬性添加到會話中。
8. @RequestHeader
該注解只能應用在方法的參數上,用于從請求頭中獲取數據
@RequestMapping("/find")
public void findUsers(@RequestHeader("Content-Type") String contentType) {//從請求頭中獲取Content-Type的值
}
9. @CookieValue
該注解只能應用在方法的參數上,用于從請求中獲取cookie的值
@RequestMapping("/find")
public void findUsers(@CookieValue("JSESSIONID") String jsessionId) {//從請cookie中獲取jsessionId的值
}
10. @ControllerAdvice
該注解只能應用在類上,表示這個類就是處理異常的控制器
/*** 異常處理的控制器*/
@ControllerAdvice //這個注解就是spring mvc提供出來做全局異常統一處理的
public class ExceptionController {
}
11. @ExceptionHandler
該注解只能應用在@ControllerAdvice或者說@RestControllerAdvice標識的類的方法上,用來處理異常
/*** 異常處理的控制器*/
@ControllerAdvice //這個注解就是spring mvc提供出來做全局異常統一處理的
public class ExceptionController {@ExceptionHandler //異常處理器@ResponseBody //響應至頁面public String handleException(Exception e){return e.getMessage();}
}
第四節 JSR-303
1. JSR-303 簡介
JSR全稱為 Java Specification Requests,表示 Java 規范提案。JSR-303是 Java 為 Java Bean 數據合法性校驗提供的標準框架,它定義了一套可標注在成員變量,屬性方法上的校驗注解。Hibernate Validatior提供了這套標準的實現。
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.1.Final</version><!-- 最新7.0.1.Final -->
</dependency>
2. 校驗注解
注解 | 解釋 | 注解 | 解釋 |
---|---|---|---|
@Null | 必須為null | @NotNull | 不能為null |
@AssertTrue | 必須為true | @AssertFalse | 必須為false |
@Min | 必須為數字,其值大于或等于指定的最小值 | @Max | 必須為數字,其值小于或等于指定的最大值 |
@DecimalMin | 必須為數字,其值大于或等于指定的最小值 | @DecimalMax | 必須為數字,其值小于或等于指定的最大值 |
@Size | 集合的長度 | @Digits | 必須為數字,其值必須再可接受的范圍內 |
@Past | 必須是過去的日期 | @Future | 必須是將來的日期 |
@Pattern | 必須符合正則表達式 | 必須是郵箱格式 | |
@Length(min=,max=) | 字符串的大小必須在指定的范圍內 | @NotEmpty | 不能為null,長度大于0 |
@Range(min=,max=,message=) | 元素必須在合適的范圍內 | @NotBlank | 不能為null,字符串長度大于0(限字符串) |
3. 應用
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>5.3.10</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.10</version>
</dependency>
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.1.Final</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope>
</dependency>
<!-- web.xml -->
<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:spring-mvc.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>
<?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:p="http://www.springframework.org/schema/p"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/" p:suffix=".jsp" /><mvc:annotation-driven><mvc:message-converters><!--處理字符串的消息轉換器--><bean class="org.springframework.http.converter.StringHttpMessageConverter" /><!--處理JSON格式的消息轉換器--><bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"><property name="supportedMediaTypes"><list><value>text/html;charset=UTF-8</value><value>application/json;charset=UTF-8</value></list></property></bean></mvc:message-converters></mvc:annotation-driven><context:component-scan base-package="com.qf.spring.controller" />
</beans>
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;import javax.validation.constraints.NotNull;public class User {@NotNull(message = "賬號不能為空")@Length(min = 8, max = 15, message = "賬號長度必須為8~15位")private String username;@NotNull(message = "密碼不能為空")@Length(min = 8, max = 20, message = "密碼長度必須為8~20位")private String password;@Range(min = 0, max = 120, message = "年齡只能在0~120歲之間")private int age;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.validation.Valid;@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping("/add")@ResponseBodypublic Object saveUser(@Valid User user, BindingResult result){if(result.hasErrors()) return result.getAllErrors();return 1;}
}
第五節 RESTFUL
1. RESTFUL 簡介
REST全稱為 Representational State Transfer,表示 表述性狀態轉移。
RESTFUL有如下特點:
- 每一個 URI 代表一種資源
- 客戶端使用GET、POST、PUT、DELETE4 個表示操作方式的動詞對服務端資源進行操作:GET用來獲取資源,POST用來新建資源(也可以用于更新資源),PUT用來更新資源,DELETE用來刪除資源
2. RESTFUL 請求
/user GET => 獲取用戶資源
/user POST => 增加用戶資源
/user PUT => 修改用戶資源
/user DELETE => 刪除用戶資源/user/{username} GET => 獲取指定用戶資源 這是RESTFUL風格中子資源的表述方式
3. Spring 對 RESTFUL 的支持
3.1 @RestController
該注解只能應用于類上,相當于@Controller 和 @ResponseBody 注解的組合。表示該類中的所有方法執行完成后所返回的結果直接向頁面輸出
3.2 @GetMapping
3.2 @PostMapping
3.2 @PutMapping
3.2 @DeleteMapping
第六節 靜態資源處理
1. 靜態資源無法訪問的原因
靜態資源包含html、js、css、圖片、字體文件等。靜態文件沒有url-pattern,所以默認是訪問不到的。之所以可以訪問,是因為tomcat中有一個全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 “/”, 所以項目中不能匹配的靜態資源請求,都由這個Servlet來處理。但在SpringMVC中DispatcherServlet也采用了"/" 作為url-pattern, 那么項目中不會再使用全局的Serlvet,這樣就造成了靜態資源不能完成訪問。
2. 處理方案
2.1 方案一
DispathcerServlet 對應的 url-pattern 修改為 “/” 以外的其他匹配樣式即可。比如 *.do, *.action。這樣修改后,發送請求時,請求URL必須匹配 .do 或者 .action。
2.2 方案二
<!-- web.xml -->
<servlet-mapping><servlet-name>default</servlet-name><url-pattern>/static/*</url-pattern></servlet-mapping>
2.2 方案三
<!-- spring-mvc.xml -->
<!--
這個handler就是處理靜態資源的,它的處理方式就是將請求轉會到tomcat中名為default的Servlet
-->
<mvc:default-servlet-handler/>
<!-- mapping是訪問路徑,location是靜態資源存放的路徑 -->
<mvc:resources mapping="/static/**" location="/static/" />
第七節 中文亂碼處理
<?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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><display-name>Archetype Created Web Application</display-name><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:spring-mvc.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><filter><filter-name>encodingFilter</filter-name><!--字符編碼過濾器--><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><!--編碼格式--><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><!--強制編碼--><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>
第八節 Spring MVC工作原理

checkMultipart(request); //檢測是否是多部分請求,這個只可能在文件上傳的時候為真getHandler(processedRequest); //獲取處理器 => 遍歷HandlerMapping,找到匹配當前請求的執行器鏈
//沒有找到執行器鏈 就直接向頁面報一個404
noHandlerFound(processedRequest, response);
//找到處理當前請求的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//控制器之前執行的攔截器將先執行,如果攔截器不通過,則方法直接結束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
//控制器處理請求,可能會得到一個ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//控制器之后的攔截器執行
mappedHandler.applyPostHandle(processedRequest, response, mv);
//處理分發的結果:這個結果就是控制器處理后的結果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//攔截器在控制器給出的結果DispatcherServlet處理后執行
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);