Spring Validator接口校驗(yàn)與全局異常處理器
Spring Validator接口校驗(yàn)
上一篇日志使用Bean Validation校驗(yàn)機(jī)制,對(duì)基本數(shù)據(jù)類型進(jìn)行校驗(yàn),方法是在實(shí)體類屬性上使用注解標(biāo)識(shí)校驗(yàn)方式,最后在Controller類中具體方法的形參里添加@Vlidated注解。Bean Validation校驗(yàn)有一個(gè)缺點(diǎn)是,我們的數(shù)據(jù)校驗(yàn)是在Java實(shí)體類里進(jìn)行約束的,如果我們有多個(gè)處理器方法需要用到同一個(gè)實(shí)體類,那么定義在實(shí)體類屬性上的校驗(yàn)規(guī)則就不好劃分了,有的處理器只需要校驗(yàn)一個(gè)屬性,而有的處理器需要校驗(yàn)多個(gè)屬性,我們不可能為每一個(gè)處理器都創(chuàng)建一個(gè)實(shí)體類。解決的方法在上一篇日志里也說(shuō)到,使用分組校驗(yàn)方式,除此之外,還可以使用Spring的Validator接口校驗(yàn),它允許我們?cè)谕獠恐付骋粚?duì)象的校驗(yàn)規(guī)則。
校驗(yàn)器實(shí)現(xiàn)類
Spring的Validator是一個(gè)接口,我們自己的校驗(yàn)實(shí)現(xiàn)類必須實(shí)現(xiàn)這個(gè)接口,才可以通過(guò)重寫方法完成自定義的校驗(yàn)規(guī)則,需要我們實(shí)現(xiàn)的方法有兩個(gè):supports()和validate()
public class UserValidator implements Validator { @Override public boolean supports(Class<?> clazz) { // 反射機(jī)制通過(guò)類的class靜態(tài)變量獲得該類的實(shí)例 return User.class.equals(clazz); } @Override public void validate(Object obj, Errors errors) { // 錯(cuò)誤信息放入errors對(duì)象 ValidationUtils.rejectIfEmpty(errors, "username", "Username.is.empty", "用戶名不允許為空。"); User user = (User) obj; if (user.getPassword() == null || user.getPassword().equals("")) { // rejectValue()參數(shù):錯(cuò)誤字段名,全局錯(cuò)誤碼,默認(rèn)錯(cuò)誤提示信息 errors.rejectValue("password", "Password.is.empty", "密碼不允許為空。"); } else if (user.getPassword().length() < 8) { errors.rejectValue("password", "Length.too.short", "密碼長(zhǎng)度不能小于八位。"); } } }
Support()方法的功能是判斷該校驗(yàn)類,是否支持被校驗(yàn)的實(shí)體類。例如我們這個(gè)校驗(yàn)類負(fù)責(zé)對(duì)User類進(jìn)行校驗(yàn),supports()方法傳入被校驗(yàn)的實(shí)體類,通過(guò)反射機(jī)制獲得User類實(shí)例,然后判斷是否與傳入的被校驗(yàn)實(shí)體類匹配。Validate()方法則是進(jìn)行校驗(yàn)的具體實(shí)現(xiàn)方法,方法參數(shù)列表中有一個(gè)Errors對(duì)象,負(fù)責(zé)往里面存放校驗(yàn)的錯(cuò)誤信息。下面就是具體的校驗(yàn)規(guī)則了,我們可以使用ValidationUtils校驗(yàn)工具類的方法進(jìn)行校驗(yàn),提供的參數(shù)依次為存放錯(cuò)誤信息對(duì)象error,校驗(yàn)的字段名(對(duì)于校驗(yàn)實(shí)體類中的屬性),全局錯(cuò)誤碼(類似于Bean Validation校驗(yàn)中根據(jù)錯(cuò)誤碼,使用外部properties的錯(cuò)誤提示信息),最后一個(gè)參數(shù)是默認(rèn)錯(cuò)誤提示信息,當(dāng)全局錯(cuò)誤碼沒(méi)有找到對(duì)應(yīng)的提示信息時(shí),使用默認(rèn)的錯(cuò)誤提示信息。
除了使用ValidationUtils校驗(yàn)工具類外,第23行還也可以使用erroe對(duì)象的方法,設(shè)置獲取校驗(yàn)錯(cuò)誤信息,參數(shù)和ValidationUtils類的方法幾乎一致。
Controller實(shí)現(xiàn)類
校驗(yàn)器類配置完后,在具體的業(yè)務(wù)邏輯處理部分,Controller類中使用。
@Controller @RequestMapping("user") public class InterfaceValidationController { @InitBinder public void initBinder(DataBinder binder) { // 為DataBinder對(duì)象設(shè)置Validator校驗(yàn)接口 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(); // 輸出所有錯(cuò)誤信息 for(ObjectError objectError : allErrors) { System.out.println("code = " + objectError.getCode() + "DefaultMessage = " + objectError.getDefaultMessage()); // 將錯(cuò)誤信息發(fā)送到前端頁(yè)面 model.addAttribute("allErrors", allErrors); } // 最后返回視圖 return "users/login"; } else { // 如果校驗(yàn)沒(méi)有錯(cuò)誤,跳轉(zhuǎn)到成功登陸的頁(yè)面 return "users/successLogin"; } }
首先需要通過(guò)initBinder()方法,在Controller類方法中進(jìn)行校驗(yàn)器的綁定,方法需要DataBinder對(duì)象參數(shù),DataBinder對(duì)象的功能是進(jìn)行數(shù)據(jù)綁定,可以將數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換,設(shè)置校驗(yàn)器等。DataBinder有一個(gè)成員變量BindingResult,進(jìn)行了數(shù)據(jù)綁定了校驗(yàn)器綁定,當(dāng)校驗(yàn)數(shù)據(jù)有錯(cuò)誤信息時(shí),就會(huì)將其放入到BindingResult對(duì)象中的Errors屬性中,Errors對(duì)象集合前面說(shuō)到,就是用來(lái)存放錯(cuò)誤信息的。在Controller具體方法的參數(shù)列表中對(duì)要校驗(yàn)的數(shù)據(jù)對(duì)象User類添加@Valid注解,標(biāo)識(shí)對(duì)該對(duì)象進(jìn)行數(shù)據(jù)校驗(yàn),接著添加BindingResult對(duì)象(這里有一點(diǎn)要注意,BindingResult參數(shù)位置必須緊跟在被校驗(yàn)的數(shù)據(jù)對(duì)象后面),當(dāng)校驗(yàn)出現(xiàn)錯(cuò)誤信息時(shí),第15行我們就可以通過(guò)該對(duì)象的hasErrors()方法判斷校驗(yàn)是否出錯(cuò),然后使用getAllErrors()方法獲取錯(cuò)誤信息進(jìn)行輸出。最后第22行我們將錯(cuò)誤信息傳到前端頁(yè)面上顯示,給用戶提示。
前端頁(yè)面測(cè)試
最后,在前端頁(yè)面進(jìn)行簡(jiǎn)單的登陸測(cè)試:
<%@ 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="登錄"/> <!-- 顯示校驗(yàn)錯(cuò)誤信息 --> <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行遍歷后臺(tái)發(fā)來(lái)的allErrors錯(cuò)誤信息集合,如果出現(xiàn)校驗(yàn)出錯(cuò),則顯示錯(cuò)誤信息。根據(jù)我們前面校驗(yàn)器的配置,對(duì)于User類對(duì)象的數(shù)據(jù)校驗(yàn),用戶名和密碼都不允許為空:
當(dāng)輸入信息正確,用戶名和密碼都不為空,且密碼長(zhǎng)度不低于8位,便可成功跳轉(zhuǎn):
全局異常處理器Exception Resolver
對(duì)于程序運(yùn)行時(shí)的錯(cuò)誤信息,我們可以通過(guò)查看日志來(lái)排查錯(cuò)誤,當(dāng)我們把錯(cuò)誤信息傳到前端頁(yè)面時(shí),為了讓用戶能看懂錯(cuò)誤原因,就需要對(duì)錯(cuò)誤信息進(jìn)行處理,在信息傳送到前端頁(yè)面前,將其捕獲。完成錯(cuò)誤信息捕獲和加工處理,就需要配置我們的異常處理器。異常處理器用來(lái)自定義程序運(yùn)行時(shí)如何解析異常,它需要自定義異常類,里面存儲(chǔ)了對(duì)應(yīng)異常的異常信息。還需要配置異常處理器,對(duì)于捕捉到的異常,如果是在自定義異常類中配置好的預(yù)期異常,則拋出相應(yīng)的錯(cuò)誤信息,否則,就進(jìn)行其他顯示。
自定義異常類
首先是自定義異常類,示例我們定義一個(gè)處理User類的異常類和異常處理器,在異常類中,設(shè)置對(duì)于User類出現(xiàn)異常時(shí)的錯(cuò)誤信息存儲(chǔ)。
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專門負(fù)責(zé)處理User類異常,它怎么指定處理User類呢?這個(gè)是在異常處理器中完成,UserException繼承了Exception類,這樣我們就可以在具體Controller方法中將其throws拋出該異常。該類中定義了一個(gè)異常信息變量,用來(lái)存放異常信息,當(dāng)異常處理器捕獲到User類的異常時(shí),通過(guò)UserException的構(gòu)造方法設(shè)置異常信息,最后拋出UserException。
異常處理器
來(lái)到異常處理器的配置,異常處理器是捕獲和處理異常的核心,在Spring MVC中,底層異常會(huì)一級(jí)一級(jí)往上拋,最后到達(dá)全局異常處理器,全局異常處理器的工作主要有四步:
- 捕獲異常,解析出異常類型。
- 如果異常是預(yù)期異常(有定義好的異常類),則拋出相應(yīng)的異常信息。
- 如果異常不是預(yù)期異常,則創(chuàng)建一個(gè)自定義異常類,拋出相應(yīng)的異常信息(如:“未知異常信息”)。
- 將異常信息綁定到前端頁(yè)面,跳轉(zhuǎn)到相應(yīng)的異常信息頁(yè)面中去。
結(jié)合上面的自定義異常類,來(lái)看看針對(duì)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,則直接創(chuàng)建該類型的異常信息 userException = (UserException) ex; } else { // 否則創(chuàng)建一個(gè)自定義的異常類型 userException = new UserException("發(fā)生未知錯(cuò)誤。"); } // 取出錯(cuò)誤信息 String errorMessage = userException.getExceptionMessage(); ModelAndView modelAndView = new ModelAndView(); // 錯(cuò)誤信息傳送到前端頁(yè)面 modelAndView.addObject("errorMessage", errorMessage); // 定向到錯(cuò)誤提示頁(yè)面 modelAndView.setViewName("errorPage/userError"); return modelAndView; } }
Spring MVC中,異常信息最終通過(guò)DispatcherServlet交由全局異常處理器處理,需要全局異常處理器實(shí)現(xiàn)HandlerExceptionResolver接口接,重寫里面的resolverException()方法完成異常處理。該方法中有兩個(gè)參數(shù)要注意,object handler指定異常處理器要處理的對(duì)象,Exception ex顯然就是接收底層拋出的異常。
在我們的異常處理器UserExceptionResolver中,第14行首先判斷異常類型是否我們的定義的預(yù)期異常UserException,如果是,則拋出,否則,創(chuàng)建一個(gè)自定義異常類型,并給出錯(cuò)誤提示“發(fā)生未知錯(cuò)誤”。最后第26行,對(duì)異常信息處理完后,發(fā)送到前端頁(yè)面進(jìn)行展示,并跳轉(zhuǎn)到錯(cuò)誤提示界面。
測(cè)試用例
最后要使用我們的異常處理器,先要在Spring配置文件中添加這個(gè)異常處理器:
<!-- 配置全局異常處理器 --> <bean class="com.mvc.exception.UserExceptionResolver"></bean>
然后在Controller類方法中做相應(yīng)的判斷,如果出現(xiàn)預(yù)期異常,則拋出:
@Controller @RequestMapping("user") public class InterfaceValidationController { @InitBinder public void initBinder(DataBinder binder) { // 為DataBinder對(duì)象設(shè)置Validator校驗(yàn)接口 binder.setValidator(new UserValidator()); } @RequestMapping("login") public String login(Model model, @Valid User user, BindingResult result) throws UserException { boolean allowVisit = checkUser(user); if (!allowVisit) { // 該用戶沒(méi)有訪問(wèn)權(quán)限,拋出異常 throw new UserException("您沒(méi)有權(quán)限訪問(wèn)!"); } List<ObjectError> allErrors = null; if (result.hasErrors()) { allErrors = result.getAllErrors(); // 輸出所有錯(cuò)誤信息 for(ObjectError objectError : allErrors) { System.out.println("code = " + objectError.getCode() + "DefaultMessage = " + objectError.getDefaultMessage()); // 將錯(cuò)誤信息發(fā)送到前端頁(yè)面 model.addAttribute("allErrors", allErrors); } // 最后返回視圖 return "users/login"; } else { // 如果校驗(yàn)沒(méi)有錯(cuò)誤,跳轉(zhuǎn)到成功登陸的頁(yè)面 return "users/successLogin"; } }
可以看到第57行最后我們還要跳轉(zhuǎn)到錯(cuò)誤頁(yè)面,將錯(cuò)誤信息顯示出來(lái):
<%@ 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>錯(cuò)誤提示</title> </head> <body> 發(fā)生異常,錯(cuò)誤信息如下:</br> <h3> <font color="red">${errorMessage}</font> </h3></br> </body> </html>
完整代碼已上傳GitHub:
https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Dubbo+Zookeeper實(shí)現(xiàn)RPC調(diào)用
這篇文章主要給大家介紹了Spring Boot整合Dubbo+Zookeeper實(shí)現(xiàn)RPC調(diào)用的步驟詳解,文中有詳細(xì)的代碼示例,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-07-07SpringMVC整合SSM實(shí)現(xiàn)表現(xiàn)層數(shù)據(jù)封裝詳解
這篇文章主要介紹了SpringMVC整合SSM實(shí)現(xiàn)表現(xiàn)層數(shù)據(jù)封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10eclipse+jdk安裝以及會(huì)遇到的問(wèn)題及解決方法
這篇文章主要介紹了eclipse+jdk安裝以及會(huì)遇到的問(wèn)題+解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小
這篇文章主要介紹了java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法
線程池(thread pool)是一種線程使用模式。線程過(guò)多或者頻繁創(chuàng)建和銷毀線程會(huì)帶來(lái)調(diào)度開(kāi)銷,進(jìn)而影響緩存局部性和整體性能。這篇文章主要介紹了一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法,需要的朋友可以參考下2019-07-07