Spring MVC的基本使用2
1、請求轉發和重定向
1.1、請求轉發(forward)
請求轉發是一種在服務器內部的資源跳轉方式。請求轉發的特點是可以轉發到本服務器內的所有路徑的資源,瀏覽器地址欄路徑不會發生變化,前端只發起一次請求,但后端轉發后的資源可以返回給前端訪問到。
在 servlet 中使用 getRequestDispatcher(xxx).forward(req, resp); 來進行請求轉發,在 springmvc 中,返回 ModelAndView 類型或者直接返回 String 實際上就是請求轉發。
示例:
@Controller public class ControllerTest01 { @RequestMapping(value = "/test.do") public ModelAndView doTest() { ModelAndView modelView = new ModelAndView(); modelView.addObject("name","張三"); modelView.addObject("age","22"); modelView.setViewName("/show.jsp"); //將轉發至show.jsp //或者可以使用 forward 關鍵字: //modelView.setViewName("forward:/WEB-INF/view/show.jsp"); //使用 forward 關鍵字時,視圖解析器將不起作用,需要寫上完整的路徑 return modelView; } }
直接返回 String 也可以做請求轉發:
@Controller public class ControllerTest02 { @RequestMapping(value = "/returnStringTest.do") public String doTest() { //框架實際上是對視圖執行forward操作 return "show1"; } }
1.2、重定向(redirect)
重定向是發一個302的狀態碼給瀏覽器,瀏覽器會自動去請求跳轉的網頁,url 會發生改變。重定向時可以參數,但是參數不像請求轉發一樣,而是會拼接到轉發后的 url 上,下一個請求并不能直接獲取到上一個請求的參數,但可以通過 url 的參數獲取到。
在 servlet 中使用 sendRedirect(url) 來進行重定向,在 springmvc 中,可以使用 redirect 關鍵字來進行重定向。
@Controller public class ControllerTest01 { @RequestMapping(value = "/test.do") public ModelAndView doTest() { ModelAndView modelView = new ModelAndView(); modelView.addObject("name","zhangsan"); modelView.addObject("age","22"); modelView.setViewName("redirect:/view/show.jsp"); //使用 forward 關鍵字時,視圖解析器將不起作用,需要寫上完整的路徑 return modelView; } }
上面在瀏覽器重定向后請求的 url 將類似于:http://xxx/view/show.jsp?name=zhangsan&age=22。
重定向不能轉發至 WEB-INF 下的資源,因為 WEB-INF 下的資源通過瀏覽器無法直接訪問。
2、異常集中處理
springmvc 框架采用的是統一的、全局的異常處理,把 controller 中的所有異常都集中到一個地方進行統一處理。采用的是 AOP 的思想,把業務邏輯和異常處理代碼分開,解耦合。
在 J2EE 項目的開發中,不管是對底層的數據庫操作過程,還是業務層的處理過程,還是控制層的處理過程,都不可避免會遇到各種可預知的、不可預知的異常需要處理。如果在每個過程都單獨處理異常,系統的代碼耦合度高,工作量大且不好統一,維護的工作量也很大。 我們可以通過 Spring MVC 統一處理異常來將所有類型的異常處理從各處理過程解耦出來,這樣既保證了相關處理過程的功能較單一,也實現了異常信息的統一處理和維護。
示例 controller 代碼:
@Controller public class ControllerTest01 { @RequestMapping(value = "/getStr.do", produces = "text/html;charset=utf-8") @ResponseBody public String doTest(String name, Integer age) throws Exception { throw new RuntimeException("手動拋出異常,這里將能被SpringMVC集中處理"); return "aaaa"; } }
然后定義一個控制器異常通知類即可,控制器異常通知類需要通過 @ControllerAdvice 和 @ExceptionHandler 注解來實現。定義控制器異常通知類后需要將該類中的包同時也加入 springmvc 的組件掃描中,否則 springmvc 無法識別該注解。
示例如下:
import exception.AgeException; import exception.NameException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice //控制器增強(即給控制器類增加功能-異常處理功能) public class GlobalExceptionHandler { //可以指定處理某些異常,比如下面我們可以指定下面的方法只處理我們自定義的NameException異常 @ExceptionHandler(value = NameException.class) public ModelAndView doNameException(Exception ex) { //可以將異常記錄到日志文件或者數據庫 ModelAndView mv = new ModelAndView(); mv.addObject("msg", "姓名必須是xxx"); mv.addObject("ex", ex); mv.setViewName("/nameError.jsp"); return mv; } //這里處理所有其他的異常 @ExceptionHandler public ModelAndView doOtherException(Exception ex) { //可以將異常記錄到日志文件或者數據庫 ModelAndView mv = new ModelAndView(); mv.addObject("msg", "異常發生了"); mv.addObject("ex", ex); System.out.println(ex); mv.setViewName("/defaultError.jsp"); return mv; } }
控制器異常通知類跟 controller 類中的方法一樣,可以返回視圖、字符串、數據,返回視圖的話前端將能看到該視圖頁面。
定義完異常集中處理類后,控制器 controller 類中的所有異常都將被集中處理(不一定要controller類中的方法手動拋出異常,也不需要controller類中的方法往上拋異常,只要定義了異常集中處理類,則controller類中的方法所有可能出現的異常都能被集中處理類捕獲到并進行處理)。
3、攔截器
Spring MVC中的攔截器和 Servlet 中的過濾器有點類似,不過功能側重點不同。攔截器可以看做是多個 controller 中公用的功能,集中到攔截器進行統一處理,使用的是 AOP 的思想。
過濾器依賴于servlet容器,是用來過濾請求參數,設置字符編碼等工作的。比如:在過濾器中修改字符編碼,在過濾器中修改HttpServletRequest的一些參數,包括:過濾低俗文字、危險字符等。而攔截器是攔截用戶請求,對請求做判斷處理的,主要用于攔截用戶請求并作相應的處理。例如通過攔截器可以進行權限驗證、記錄請求信息的日志、判斷用戶是否登錄等。
攔截器依賴于spring mvc框架,在實現上基于Java的反射機制,屬于面向切面編程(AOP)的一種運用。攔截器需要實現 HandlerInterceptor 接口,一個項目中可以有多個攔截器,他們一起作用攔截用戶的請求。攔截器所攔截的請求必須被中央調度器處理,否則無法攔截該請求。
攔截器常見的應用場景:
- 日志記錄:記錄請求信息的日志,以便進行信息監控、信息統計、計算PV(Page View)等。
- 權限檢查:如登錄檢測,進入處理器檢測是否登錄,如果沒有直接返回到登錄頁面;
- 性能監控:有時候系統在某段時間莫名其妙的慢,可以通過攔截器在進入處理器之前記錄開始時間,在處理完后記錄結束時間,從而得到該請求的處理時間(如果有反向代理,如apache可以自動記錄);
- 通用行為:讀取cookie得到用戶信息并將用戶對象放入請求,從而方便后續流程使用,還有如提取Locale、Theme信息等,只要是多個Controller中的處理方法都需要的,我們就可以使用攔截器實現。
- OpenSessionInView:如Hibernate,在進入處理器打開Session,在完成后關閉Session。
3.1、攔截器的回調方法(preHandle、postHandle、afterCompletion)
攔截器有3個回調方法:
(1)preHandle():預處理回調方法,實現處理器的預處理(如登錄檢查)。該方法在控制器方法之前執行。一般可以在該方法中獲取用戶請求的信息,驗證請求是否符合要求,可以驗證用戶是否登錄,驗證用戶權限等等。preHandle() 方法可以說是整個項目的入口、門戶。
該方法有三個參數,preHandle(HttpServletRequest request, HttpServletResponse response, Object handler),第三個參數是被攔截的控制器 controller 對象。該方法的返回值為布爾值,true表示繼續流程(如調用下一個攔截器或處理器方法),false表示流程中斷(如登錄檢查失敗),即不會繼續調用其他的攔截器或處理器。如果返回值為 false,則該請求不會執行處理器方法,此時我們需要通過 preHandle 方法中的 response來產生響應。
(2)postHandle():后處理回調方法,該方法會在控制器方法調用之后,且解析視圖(或數據響應)之前執行。可以通過此方法對請求域中的模型和視圖做出進一步的修改。
該方法有四個參數,第三個參數是被攔截的控制器 controller 對象,第四個參數是處理器方法的 ModelAndView 返回值,可以修改 ModelAndView 中的數據和視圖,對最終返回結果產生影響,可以用來對原來的執行結果做二次修正。只有 preHandle 返回 true 攔截器才會執行 postHandle。
(3)afterCompletion():整個請求處理完畢后的回調方法。該方法會在整個請求完成,即視圖渲染結束之后執行(框架中認為對視圖執行了 forward 則認為請求已處理完成 )。可以通過此方法實現一些資源清理、記錄日志信息等工作。如性能監控中我們可以在此記錄結束時間并輸出消耗時間,還可以進行一些資源清理,類似于try-catch-finally中的finally。只有 preHandle() 返回 true 攔截器才會執行 afterCompletion。
3.2、攔截器的使用
通過實現 HandlerInterceptor 接口我們可以定義一個攔截器:
例如,在任意一個包內定義一個攔截器類:
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyHandler implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("執行攔截器的preHandle()方法"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("執行攔截器的postHandle()方法"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("執行攔截器的afterCompletion()方法"); } }
定義攔截器后我們需要在 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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> ... <!--配置攔截器。攔截器可以有多個--> <mvc:interceptors> <!--攔截器1--> <mvc:interceptor> <!--指定攔截的請求uri地址。 **:表示任意字符,如 /** 表示攔截任意請求 --> <mvc:mapping path="/user/**"/> <!--聲明攔截器對象,class指向攔截器類的完整類名--> <bean class="handler.MyHandler"/> </mvc:interceptor> <!--攔截器2--> <!--<mvc:interceptor>--> <!-- <mvc:mapping path="/hello"/>--> <!-- <bean class="com.ma.interceptor.Interceptor2"/>--> <!--</mvc:interceptor>--> </mvc:interceptors> </beans>
由此,攔截器定義完成。我們可以定義一個 controller 來驗證攔截器的作用。
@Controller @RequestMapping("/user") public class ControllerTest02 { @RequestMapping(value = "/test.do") @ResponseBody public Student doTest02(Student student) { System.out.println("執行test01.do方法"); return student; } @RequestMapping(value = "/test02.do") @ResponseBody public ComplexDomain doTest03(@RequestBody ComplexDomain complexDomain) { System.out.println("執行test02.do方法"); return complexDomain; } }
我們訪問 user/test01.do 或者 user/test02.do,可以發現控制臺中的輸出為:
執行攔截器的preHandle()方法
執行test01.do方法
執行攔截器的postHandle()方法
執行攔截器的afterCompletion()方法
springboot 配置攔截器參考:http://www.rzrgm.cn/wx60079/p/12841733.html
3.3、攔截器的執行順序
3.3.1、單個攔截器的執行順序
單個攔截器的執行流程:

3.3.2、多個攔截器的執行順序
多個攔截器(假設有兩個攔截器Interceptor1和Interceptor2,并且在配置文件中, Interceptor1攔截器配置在前),則在程序中的執行流程如下圖所示:

多個攔截器時,只有作用于某一請求的所有的攔截器的 prehandle() 方法都返回了 true,該請求的控制器方法才會執行。
執行順序的形象理解可以看做是前面配置的攔截器包裹了后面定義的攔截器。
示例:
假設定義了兩個攔截器對同一個路徑的請求都進行了攔截,并且這兩個攔截器的 perhandler() 方法都返回了 true。
<!--配置攔截器。攔截器可以有多個,框架中用ArrayList保存多個攔截器,按照聲明的先后順序放到集合中--> <mvc:interceptors> <!--<bean class="com.ma.interceptor.CustomeInterceptor" />--> <!--攔截器1--> <mvc:interceptor> <!--指定攔截的請求uri地址。 **:表示任意字符,如 /** 表示攔截任意請求 --> <mvc:mapping path="/user/**"/> <!--聲明攔截器對象--> <bean class="handler.MyHandler"/> </mvc:interceptor> <!--攔截器2--> <mvc:interceptor> <mvc:mapping path="/user/**" /> <bean class="handler.MyHandler02"/> </mvc:interceptor> </mvc:interceptors>
假如此時我們訪問 user/test01.do 請求,則執行順序類似如下:

4、Springmvc的執行流程
Spring MVC的執行流程:

執行流程:
- 用戶發送請求到前端控制器 DispatcherServlet
- DispatcherServlet 收到請求,把請求轉發給處理器映射器 HandlerMapping
- 處理映射器根據請求的 url 找到對應的處理器,生成一個叫做處理器執行鏈 HandlerExecutionChain 的類返回給DispatcherServlet,該類保存著處理器對象和項目中所有的處理器攔截器
- DispatcherServlet 根據處理器執行鏈中的處理器找到對應的適配器,并將處理器對象轉交給處理器適配器
- 處理器適配器 HandlerAdapter 調用處理器對象 Handler 的方法
- Handler(Controller)執行完成后返回 ModelAndView 給處理器適配器
- 處理器適配器 HandlerAdapter 返回 ModelAndView 給前端控制器
- DispatcherServlet 將返回的 ModelAndView 派送到 ViewResolve(視圖解析器)解析
- 視圖解析器組成視圖的完整路徑,并創建 view 對象,解析完之后返回 View 對象給 DispatcherServlet
- DispatcherServlet 拿到 view 對象,對其進行數據填充
- 響應用戶,返回給瀏覽器

浙公網安備 33010602011771號