Spring Validator介面校驗與全域性異常處理器
Spring Validator介面校驗
上一篇日誌使用Bean Validation校驗機制,對基本資料型別進行校驗,方法是在實體類屬性上使用註解標識校驗方式,最後在Controller類中具體方法的形參裡新增@Vlidated註解。Bean Validation校驗有一個缺點是,我們的資料校驗是在Java實體類裡進行約束的,如果我們有多個處理器方法需要用到同一個實體類,那麼定義在實體類屬性上的校驗規則就不好劃分了,有的處理器只需要校驗一個屬性,而有的處理器需要校驗多個屬性,我們不可能為每一個處理器都建立一個實體類。解決的方法在上一篇日誌裡也說到,使用分組校驗方式,除此之外,還可以使用Spring的Validator介面校驗,它允許我們在外部指定某一物件的校驗規則。
校驗器實現類
Spring的Validator是一個介面,我們自己的校驗實現類必須實現這個介面,才可以通過重寫方法完成自定義的校驗規則,需要我們實現的方法有兩個:supports()和validate()
public class UserValidator implements Validator { @Override public boolean supports(Class<?> clazz) { // 反射機制通過類的class靜態變數獲得該類的例項 return User.class.equals(clazz); } @Override public void validate(Object obj,Errors errors) { // 錯誤資訊放入errors物件 ValidationUtils.rejectIfEmpty(errors,"username","Username.is.empty","使用者名稱不允許為空。"); User user = (User) obj; if (user.getPassword() == null || user.getPassword().equals("")) { // rejectValue()引數:錯誤欄位名,全域性錯誤碼,預設錯誤提示資訊 errors.rejectValue("password","Password.is.empty","密碼不允許為空。"); } else if (user.getPassword().length() < 8) { errors.rejectValue("password","Length.too.short","密碼長度不能小於八位。"); } } }
Support()方法的功能是判斷該校驗類,是否支援被校驗的實體類。例如我們這個校驗類負責對User類進行校驗,supports()方法傳入被校驗的實體類,通過反射機制獲得User類例項,然後判斷是否與傳入的被校驗實體類匹配。Validate()方法則是進行校驗的具體實現方法,方法引數列表中有一個Errors物件,負責往裡面存放校驗的錯誤資訊。下面就是具體的校驗規則了,我們可以使用ValidationUtils校驗工具類的方法進行校驗,提供的引數依次為存放錯誤資訊物件error,校驗的欄位名(對於校驗實體類中的屬性),全域性錯誤碼(類似於Bean Validation校驗中根據錯誤碼,使用外部properties的錯誤提示資訊),最後一個引數是預設錯誤提示資訊,當全域性錯誤碼沒有找到對應的提示資訊時,使用預設的錯誤提示資訊。
除了使用ValidationUtils校驗工具類外,第23行還也可以使用erroe物件的方法,設定獲取校驗錯誤資訊,引數和ValidationUtils類的方法幾乎一致。
Controller實現類
校驗器類配置完後,在具體的業務邏輯處理部分,Controller類中使用。
@Controller @RequestMapping("user") public class InterfaceValidationController { @InitBinder public void initBinder(DataBinder binder) { // 為DataBinder物件設定Validator校驗介面 binder.setValidator(new UserValidator()); } @RequestMapping("login") public String login(Model model,@Valid User user,BindingResult result) { List<ObjectError> allErrors = null; if (result.hasErrors()) { allErrors = result.getAllErrors(); // 輸出所有錯誤資訊 for(ObjectError objectError : allErrors) { System.out.println("code = " + objectError.getCode() + "DefaultMessage = " + objectError.getDefaultMessage()); // 將錯誤資訊傳送到前端頁面 model.addAttribute("allErrors",allErrors); } // 最後返回檢視 return "users/login"; } else { // 如果校驗沒有錯誤,跳轉到成功登陸的頁面 return "users/successLogin"; } }
首先需要通過initBinder()方法,在Controller類方法中進行校驗器的繫結,方法需要DataBinder物件引數,DataBinder物件的功能是進行資料繫結,可以將資料進行型別轉換,設定校驗器等。DataBinder有一個成員變數BindingResult,進行了資料綁定了校驗器繫結,當校驗資料有錯誤資訊時,就會將其放入到BindingResult物件中的Errors屬性中,Errors物件集合前面說到,就是用來存放錯誤資訊的。在Controller具體方法的引數列表中對要校驗的資料物件User類新增@Valid註解,標識對該物件進行資料校驗,接著新增BindingResult物件(這裡有一點要注意,BindingResult引數位置必須緊跟在被校驗的資料物件後面),當校驗出現錯誤資訊時,第15行我們就可以通過該物件的hasErrors()方法判斷校驗是否出錯,然後使用getAllErrors()方法獲取錯誤資訊進行輸出。最後第22行我們將錯誤資訊傳到前端頁面上顯示,給使用者提示。
前端頁面測試
最後,在前端頁面進行簡單的登陸測試:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>登入介面</title> </head> <body> <from action="login.action" method="post"> 使用者名稱:<input type="text" name="username" /></br> 密碼: <input type="password" name="password" /></br> <input type="submit" value="登入"/> <!-- 顯示校驗錯誤資訊 --> <c:if test="${allErrors != null }"> <c:forEach items="${allErrors}" var="error"> </br><font color="red">${error.defaultMessage}</font> </c:forEach> </c:if> </from> </body> </html>
第15行遍歷後臺發來的allErrors錯誤資訊集合,如果出現校驗出錯,則顯示錯誤資訊。根據我們前面校驗器的配置,對於User類物件的資料校驗,使用者名稱和密碼都不允許為空:
當輸入資訊正確,使用者名稱和密碼都不為空,且密碼長度不低於8位,便可成功跳轉:
全域性異常處理器Exception Resolver
對於程式執行時的錯誤資訊,我們可以通過檢視日誌來排查錯誤,當我們把錯誤資訊傳到前端頁面時,為了讓使用者能看懂錯誤原因,就需要對錯誤資訊進行處理,在資訊傳送到前端頁面前,將其捕獲。完成錯誤資訊捕獲和加工處理,就需要配置我們的異常處理器。異常處理器用來自定義程式執行時如何解析異常,它需要自定義異常類,裡面儲存了對應異常的異常資訊。還需要配置異常處理器,對於捕捉到的異常,如果是在自定義異常類中配置好的預期異常,則丟擲相應的錯誤資訊,否則,就進行其他顯示。
自定義異常類
首先是自定義異常類,示例我們定義一個處理User類的異常類和異常處理器,在異常類中,設定對於User類出現異常時的錯誤資訊儲存。
package com.mvc.exception; public class UserException extends Exception { private static final long serialVersionUID = 1L; private String exceptionMessage; public UserException(String exceptionMessage) { super(exceptionMessage); this.exceptionMessage = exceptionMessage; } public String getExceptionMessage() { return exceptionMessage; } public void setExceptionMessage(String exceptionMessage) { this.exceptionMessage = exceptionMessage; } }
自定義異常類UserException專門負責處理User類異常,它怎麼指定處理User類呢?這個是在異常處理器中完成,UserException繼承了Exception類,這樣我們就可以在具體Controller方法中將其throws丟擲該異常。該類中定義了一個異常資訊變數,用來存放異常資訊,當異常處理器捕獲到User類的異常時,通過UserException的構造方法設定異常資訊,最後丟擲UserException。
異常處理器
來到異常處理器的配置,異常處理器是捕獲和處理異常的核心,在Spring MVC中,底層異常會一級一級往上拋,最後到達全域性異常處理器,全域性異常處理器的工作主要有四步:
- 捕獲異常,解析出異常型別。
- 如果異常是預期異常(有定義好的異常類),則丟擲相應的異常資訊。
- 如果異常不是預期異常,則建立一個自定義異常類,丟擲相應的異常資訊(如:“未知異常資訊”)。
- 將異常資訊繫結到前端頁面,跳轉到相應的異常資訊頁面中去。
結合上面的自定義異常類,來看看針對User類的異常處理器的配置:
package com.mvc.exception; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; public class UserExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) { // 首先解析出異常的型別 UserException userException = null; if (ex instanceof UserException) { // 如果異常型別是UserException,則直接建立該型別的異常資訊 userException = (UserException) ex; } else { // 否則建立一個自定義的異常型別 userException = new UserException("發生未知錯誤。"); } // 取出錯誤資訊 String errorMessage = userException.getExceptionMessage(); ModelAndView modelAndView = new ModelAndView(); // 錯誤資訊傳送到前端頁面 modelAndView.addObject("errorMessage",errorMessage); // 定向到錯誤提示頁面 modelAndView.setViewName("errorPage/userError"); return modelAndView; } }
Spring MVC中,異常資訊最終通過DispatcherServlet交由全域性異常處理器處理,需要全域性異常處理器實現HandlerExceptionResolver介面接,重寫裡面的resolverException()方法完成異常處理。該方法中有兩個引數要注意,object handler指定異常處理器要處理的物件,Exception ex顯然就是接收底層丟擲的異常。
在我們的異常處理器UserExceptionResolver中,第14行首先判斷異常型別是否我們的定義的預期異常UserException,如果是,則丟擲,否則,建立一個自定義異常型別,並給出錯誤提示“發生未知錯誤”。最後第26行,對異常資訊處理完後,傳送到前端頁面進行展示,並跳轉到錯誤提示介面。
測試用例
最後要使用我們的異常處理器,先要在Spring配置檔案中新增這個異常處理器:
<!-- 配置全域性異常處理器 --> <bean class="com.mvc.exception.UserExceptionResolver"></bean>
然後在Controller類方法中做相應的判斷,如果出現預期異常,則丟擲:
@Controller @RequestMapping("user") public class InterfaceValidationController { @InitBinder public void initBinder(DataBinder binder) { // 為DataBinder物件設定Validator校驗介面 binder.setValidator(new UserValidator()); } @RequestMapping("login") public String login(Model model,BindingResult result) throws UserException { boolean allowVisit = checkUser(user); if (!allowVisit) { // 該使用者沒有訪問許可權,丟擲異常 throw new UserException("您沒有許可權訪問!"); } List<ObjectError> allErrors = null; if (result.hasErrors()) { allErrors = result.getAllErrors(); // 輸出所有錯誤資訊 for(ObjectError objectError : allErrors) { System.out.println("code = " + objectError.getCode() + "DefaultMessage = " + objectError.getDefaultMessage()); // 將錯誤資訊傳送到前端頁面 model.addAttribute("allErrors",跳轉到成功登陸的頁面 return "users/successLogin"; } }
可以看到第57行最後我們還要跳轉到錯誤頁面,將錯誤資訊顯示出來:
<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta charset="utf-8"> <title>錯誤提示</title> </head> <body> 發生異常,錯誤資訊如下:</br> <h3> <font color="red">${errorMessage}</font> </h3></br> </body> </html>
完整程式碼已上傳GitHub:
https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。