Spring MVC溫故而知新 – 參數綁定、轉發與重定向、異常處理、攔截器
請求參數綁定
當用戶發送請求時,根據Spring MVC的請求處理流程,前端控制器會請求處理器映射器返回一個處理器,然後請求處理器適配器之心相應的處理器,此時處理器映射器會調用Spring Mvc 提供的參數綁定組件將請求的key/value 數據綁定到Controller處理器方法對應的形參上。Spring MVC使用Converter轉換器可以進行各種類型的轉換,也可自定義Converter轉換器,Spring MVC默認轉換器支持的類型有HttpServletRequest、HttpServletResponse、HttpSession、Model、ModelMap。其中Model是一個接口,ModelMap是一個接口實現,作用是將model數據填充到request。
簡單類型,自定義類型
//localhost:8080/springMvcNext/product/infoa?id=1 @RequestMapping("infoa") public String productInfoa(Model model, Integer id) { model.addAttribute("message", "productid:" + id); return "product/info"; }
備註:如果url中參數名不是id,則不會綁定成功,需要通過使用註解RequestParam綁定參數
自定義類型傳遞,使用pojo傳遞(Product)
@RequestMapping(value="infob",method = RequestMethod.POST) public String productInfob(Model model, Product product) { model.addAttribute("message", "product-price:" + product.getPrice()+"product-name:" + product.getProductName()); return "product/info"; }
使用註解綁定參數
通過RequestParam註解綁定參數形參名與入參不一致的參數,RerquestParam有三個參數屬性,value參數名,指定要綁定的入參名,required是否必須,默認為false,defaultValue屬性,用於沒有傳遞時賦默認值。
//http://localhost:8080/springMvcNext/product/info?productId=1&name=fgsg @RequestMapping("info") public String productInfo(Model model, @RequestParam(name = "name", defaultValue = "test") String productName, @RequestParam(required = true) Integer productId) { model.addAttribute("message", "name:" + productName + " productid:" + productId); return "product/info"; }
通過RequestHeader註解獲取請求頭的信息,RequestHeader同樣有三個參數屬性value,required,defaultvalue
// http://localhost:8080/springMvcNext/product/info2 // 輸出產品信息:browser:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 language:zh-CN,zh;q=0.9 @RequestMapping("info2") public String productInfo2(Model model, @RequestHeader("User-Agent") String browser, @RequestHeader(value = "Accept-Language", required = false, defaultValue = "null") String language) { model.addAttribute("message", "browser:" + browser + " language:" + language); return "product/info"; }
通過CookieValue註解獲取請求頭的信息
//http://localhost:8080/springMvcNext/product/info3 //輸出:產品信息:JSESSIONID:0FD3AFA5E445DADACBC1F07568970FEC @RequestMapping("info3") public String productInfo3(Model model, @CookieValue("JSESSIONID") String cookie) { model.addAttribute("message", "JSESSIONID:" + cookie); return "product/info"; }
通過HttpServletRequest獲取參數
使用HttpServletRequest獲取請求參數,當客戶端通過HTTP協議訪問服務器時,HTTP請求頭中的所有信息都封裝在這個對象中,開發人員通過這個對象的方法,可以獲得客戶這些信息,HttpServletRequest可以用於參數解析,Cookie讀取,http請求字段,文件上傳
@RequestMapping("info4") public String productInfo(String houseUnitInfo, HttpServletRequest request, HttpServletResponse response) throws IOException { String requestStr = charReader(request); System.out.println(requestStr); return "product/info"; } private String charReader(HttpServletRequest request) throws IOException { BufferedReader br = request.getReader(); String str, wholeStr = ""; while ((str = br.readLine()) != null) { wholeStr += str; } // System.out.println(wholeStr); return wholeStr; }
測試:
結果:
Action返回值
返回ModelAndView
返回ModelAndView可以指定視圖名和model數據,ModelAndView提供的addObject方法來給這個模型添加數據,添加的是一個鍵值對的數據
@RequestMapping("info5") public ModelAndView productInfo5() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("product/detail"); modelAndView.addObject("message", "return modelandview"); //modelAndView.addObject("xxx", "yyy"); return modelAndView; }
返回void,Map,Model
返回void,Map,Model 時,返回對應的邏輯視圖名稱就是請求url,仍然遵循:prefix前綴+視圖名稱 +suffix後綴組成
//1.返回Map// 訪問視圖: /springMvcNext/WEB-INF/view/product/detail.jsp @RequestMapping("detail") public Map<String, Object> detail2313() { Map<String, Object> map = new HashMap<String, Object>(); map.put("message", "product detail"); return map; } //2.返回void //返回void時,則響應的視圖頁面對應為訪問地址 //訪問視圖: /springMvcNext/WEB-INF/view/product/info6.jsp @RequestMapping("info6") public void productInfo6() { }
但輸出流中存在輸出內容時,則不會去查找視圖,而是將輸入流中的內容直接響應到客戶端,響應的內容類型是純文本
@RequestMapping("info7") public void productInfo7(HttpServletResponse response) throws IOException { response.getWriter().write("<h2>void method</h2>");//直接相應結果 } @RequestMapping("info8") public void productInfo8(HttpServletResponse response) throws IOException { response.sendRedirect("detail"); //重定向 訪問:http://localhost:8080/springMvcNext/product/detail }
//3. 返回Model model對象會用於頁面渲染,視圖路徑使用方法名,與void類似。示例代碼如下: @RequestMapping("info9") public Model productInfo9(Model model) { model.addAttribute("message", "product detail"); return model; }
返回String(視圖名)
返回視圖名:Controller類方法返回字符串可以指定邏輯視圖名,通過視圖解析器解析為物理視圖地址。
@RequestMapping("info10") public String productInfo10(Model model) { model.addAttribute("message", "productInfo10"); return "product/detail"; }
Spring MVC轉發與重定向
使用 <mvc:view-controller>標簽轉發
Spring MVC中對與WEB-INF目錄下面的JSP頁面,不能直接通過URL訪問。需要通過轉發的方式,而我們一般都是在控制器中做轉發映射,對應一些我們不需要其他操作的JSP頁面,我們可以使用<mvc:view-controller path=""/>來配置,這樣就可以不用再控制器中再去做轉發映射。
<!-- 配置直接進行轉發的頁面,無須進入handler方法 --> <mvc:view-controller path="home" /> <mvc:view-controller path="order/info" />
訪問:http://localhost:8080/springMvcNext/order/info 和 http://localhost:8080/springMvcNext/home 不經過處理器
使用forward或者redirect進行視圖轉發與重定
重定向:Spring mvc中可以在返回的結果前加上一個前綴“redirect:”,可以重定向到一個指定的頁面也可以是另一個action
轉發:Springmvc中返回結果前加“foword”前綴,註意:轉發是一次請求(相同的request),地址欄的URL不會改變
//重定向 //訪問:http://localhost:8080/springMvcNext/product/redirecttest 時Url將跳轉http://localhost:8080/springMvcNext/product/info10?redirectparas=test+redirect @RequestMapping("redirecttest") public String redirecttest(Model model) { model.addAttribute("redirectparas", "test redirect"); //帶參數跳轉 return "redirect:/product/info10"; } //轉發 //訪問http://localhost:8080/springMvcNext/product/forwardtest url不會跳轉 @RequestMapping("forwardtest") public String forwardtest(Model model){ model.addAttribute("forwardparas", "test forward"); //帶參數跳轉 return "forward:/product/info10"; }
異常處理
Spring MVC中通過使用@controlleradvice + @ ExceptionHandler 兩個註解可以實現全局的異常捕捉。
@ExceptionHandler註解的作用是當出現其定義的異常時進行處理的方法,其可以使用springmvc提供的數據綁定,比如註入HttpServletRequest等,還可以接受一個當前拋出的Throwable對象
@ControllerAdvice 註解可以把異常處理器應用到所有控制器 @Controller ,而不是@Controller註解的單個控制器,該異常處理器對當前控制器的所有方法有效;如果單獨某個控制器需要自定義處理異常,不用頂層的異常處理器,可以在當前控制器內用 @ExceptionHandler 註解 ,這樣當前控制器的異常處理就在當前類中。
備註:使用ControllerAdvice註解類裏面的異常的處理的優先級低於直接定義在處理方法的類中
實現一個異常處理器:
@ControllerAdvice public class ExceptionHandlers { @ExceptionHandler({ArithmeticException.class}) public ModelAndView toException(Exception e){ ModelAndView mv = new ModelAndView("home"); System.out.println("gobal handler exception"); //雖然不能使用Map往request中存值,但是可以使用下面的方法 mv.addObject("error", e); System.out.println(e); return mv; } }
控制器
@Controller @RequestMapping("exception") public class ExceptionController { // 示例1 @RequestMapping("test") public ModelAndView test() { System.out.println(10/0); //拋異常 return new ModelAndView("order/info", "message", "test exception"); } }
攔截器
Spring MVC提供了Interceptor攔截機制,用於請求的預處理和後處理。在Spring MVC中定義一個攔截器有兩種方法:第一種是實現HandlerInterceptor接口,或者繼承實現了HandlerInterceptor接口的類例如(HandlerInterceptorAdapter);第二種方法是實現Spring的WebRequestInterceptor接口(該接口是針對請求的攔截器接口,接口方法參數中沒有response),或者繼承實現了WebrequestInterceptor的類。兩種方式都是在Handlerde 執行周期內進行攔截操作。
如果要實現HandlerInterceptor接口,需要實現三個方法,preHandle、postHandle、afterCompletion
preHandle方法在執行Handler方法之前執行,返回false表示攔截請求,不在執行後續邏輯,可以用來做權限,日誌等。
postHandle方法在執行Handler方法之後,返回modelAndView之前執行,由於該方法會在DispatcherServlet進行返回視圖渲染之前被調用,所以此方法多被用於同一處理返回視圖,例如將公用的模型數據添加到視圖,或者根據其他情況制定公用的視圖。
afterCompletion方法在執行完Handler之後執行,由於是在Controller方法執行完畢後執行該方法,所以該方法適合進行統一的異常或者日誌處理操作。
實現HandlerInterceptor接口之後需要在Spring的類加載配置文件中配置攔截器實現類,才能使攔截器起到攔截的效果。HandlerInterceptor類加載配置有兩種方式,分別是”針對HandlerMapping配置”和 全局配置。
針對HandlerMapping配置需要在某個處理器映射器配置中將攔截器作為參數配置進去,之後通過此處理器映射器的handler就會使用配置好的攔截器,配置如下:
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="hInterceptor1" /> <ref bean="hInterceptor2" /> </list> </property> <property name="order" value="1"></property> </bean> <bean id="hInterceptor1" class="com.sl.interceptors.TestInterceptor"></bean> <bean id="hInterceptor2" class="com.sl.interceptors.TestOrderInterceptor"></bean>
全局配置
<!-- 配置自定義的攔截器 --> <mvc:interceptors> <bean class="com.sl.interceptors.TestInterceptor"></bean> </mvc:interceptors>
實現一個攔截器:
@Component public class TestInterceptor implements HandlerInterceptor { /** * 當目標方法執行之前,執行此方法,返回false,則不再執行後續邏輯postHandle、afterCompletion */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("First preHandle 最先執行.."); return true; } /** * 執行目標方法之後,渲染視圖之前調。 在轉向jsp頁面之前, 可以對請求域中的屬性,或者視圖進行修改 */ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("First postHandle 執行目標方法之後,渲染視圖之前調。 在轉向jsp頁面之前,"); } /** * 在渲染視圖之後被調用,可以進行日誌處理 */ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("First afterCompletion 渲染視圖之後調用"); } }
運行controller則可以看到攔截器執行記錄。
如果定義多個攔截器,則執行順序如下:
1. preHandle是按配置文件中的順序執行的
2. postHandle是按配置文件中的倒序執行的
3. afterCompletion是按配置文件中的倒序執行的
測試驗證:
攔截器的指定範圍:配置攔截器時可以根據需要制定攔截器作用範圍,針對特定處理器或方法進行攔截。
<!-- 配置攔截器 --> <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根節點下則攔截所有的請求 --> <!-- 定義在mvc:interceptor下面的表示是對特定的請求才進行攔截的 --> <mvc:interceptors> <mvc:interceptor>
<!-- 指定攔截器作用路徑 --> <mvc:mapping path="/product/*" /> <bean class="com.sl.interceptors.TestInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/order/*" /> <bean class="com.sl.interceptors.TestOrderInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
<mvc:exclude-mapping path=""/> 表示針對該Path不攔截 ,<mvc:mapping path=""/> 表示針對該Path攔截,Path可以使用通配符。
Spring MVC溫故而知新 – 參數綁定、轉發與重定向、異常處理、攔截器