聲明:本文來源於MLDN培訓視頻的課堂筆記,寫在這里只是為了方便查閱。
1、概念:
SpringBoot 錯誤處理
2、具體內容
在之前的程序里面如果一旦出現了錯誤之后就會出現一堆的大白板,這個白板會有一些錯誤信息(雖然這些錯誤信息你可能 看不懂,但是這些錯誤信息依然要告訴給用戶)。在 SpringBoot 里面針對於錯誤的處理一共提供有三種方式:數據驗證錯誤、錯誤 頁指派以及全局異常的處理。
2.1、數據驗證
現在假設說要進行表單信息提交,肯定需要有一個表單,而后這個表單要將數據提交到 VO 類中,所以現在的基本實現如下:
1、 建立一個 Member.java 的 VO 類:
package cn.study.microboot.vo; import java.io.Serializable; import java.util.Date; @SuppressWarnings("serial") public class Member implements Serializable { private String mid ; private Integer age ; private Double salary ; private Date birthday ; public String getMid() { return mid; } public void setMid(String mid) { this.mid = mid; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Member [mid=" + mid + ", age=" + age + ", salary=" + salary + ", birthday=" + birthday + "]"; } }
2、 由於此時的程序之中需要進行日期的轉換處理操作,那么就需要為其做一個轉換處理的格式配置,修改 AbstractBaseController 類,追加如下的轉換操作方法綁定:
@InitBinder public void initBinder(WebDataBinder binder) { // 在本程序里面需要針對於日期格式進行處理 // 首先建立一個可以將字符串轉換為日期的工具程序類 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ; // 明確的描述此時需要注冊一個日期格式的轉化處理程序類 binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(sdf, true)); }
3、 建立一個 MemberController 程序類,負責實現 Member 的控制層處理操作。
package cn.study.microboot.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import cn.study.microboot.util.controller.AbstractBaseController; import cn.study.microboot.vo.Member; @Controller public class MemberController extends AbstractBaseController { @RequestMapping(value = "/addPre", method = RequestMethod.GET) public String addPre() { // 增加前的准備操作路徑 return "member_add" ; } @RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public Object add(Member vo) { // 增加前的准備操作路徑 return vo ; } }
4、 編寫一個頁面進行用戶的表單填寫(在 src/main/view/templates 下建立):member_add.html;
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>SpringBoot模版渲染</title> <link rel="icon" type="image/x-icon" href="/images/study.ico"/> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> </head> <body> <form action="add" method="post"> 用戶郵箱:<input type="text" name="mid" value="studyjava@163.com"/><br/> 用戶年齡:<input type="text" name="age" value="18"/><br/> 用戶工資:<input type="text" name="salary" value="1000"/><br/> 用戶生日:<input type="text" name="birthday" value="2010-10-10"/><br/> <input type="submit" value="提交"/> <input type="reset" value="重置"/> </form> </body> </html>
5、 此時的代碼只是一個最為普通的處理操作,但是這個時候對於該程序也是存在有如下問題的:
· 如果某些數據沒有輸入,則內容是 null,如果要進行嚴格控制,這些 null 不應該存在;
· 某些數據需要進行格式驗證,例如:用戶名應該是郵箱,這個的信息應該進行郵箱驗證;
所以現在如果要想進行這些的驗證,SpringBoot 里面有默認的支持,只不過這種支持未必是最好的,在 SpringBoot 里面為了 方便用戶編寫驗證專門提供有一個 hibernate-validation.jar 工具包,這個工具包是由 hibernate 開發框架提供的。
6、 如果要想進行驗證,那么首先要解決的問題就必須是錯誤的提示信息問題,而在 SpringBoot 里面對於錯誤信息的保存,都要 求其保存在 ValidationMessages.properties 文件,在“src/main/resources”目錄中建立此文件;
member.mid.notnull.error=用戶名不允許為空!
member.mid.email.error=用戶名的注冊必須輸入正確的郵箱!
member.mid.length.error=用戶名的格式錯誤!
member.age.notnull.error=年齡不允許為空
member.age.digits.error=年齡必須是合法數字!
member.salary.notnull.error=工資不允許為空!
member.salary.digits.error=工資必須是合法數字!
member.birthday.notnull.error=生日不允許為空!
提示:你一個表單就需要編寫這么多的配置項,那么如果要有幾百個表單呢?這樣的配置項太可怕了,所以最好的數據檢測還是利 用攔截器處理最合適。
7、 修改 Member.java 程序類追加驗證的處理方式:
package cn.study.microboot.vo; import java.io.Serializable; import java.util.Date; import javax.validation.constraints.Digits; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; @SuppressWarnings("serial") public class Member implements Serializable { @NotNull(message="{member.mid.notnull.error}") @Email(message="{member.mid.email.error}") @Length(min=6,message="{member.mid.length.error}") private String mid ; @NotNull(message="{member.age.notnull.error}") @Digits(integer=3,fraction=0,message="{member.age.digits.error}") private Integer age ; @NotNull(message="{member.salary.notnull.error}") @Digits(integer=20,fraction=2,message="{member.salary.digits.error}") private Double salary ; @NotNull(message="{member.birthday.notnull.error}") private Date birthday ; public String getMid() { return mid; } public void setMid(String mid) { this.mid = mid; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Member [mid=" + mid + ", age=" + age + ", salary=" + salary + ", birthday=" + birthday + "]"; } }
8、 修改 MemberController 類中的 add()方法來觀察錯誤信息的顯示:
package cn.study.microboot.controller; import java.util.Iterator; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import cn.study.microboot.util.controller.AbstractBaseController; import cn.study.microboot.vo.Member; @Controller public class MemberController extends AbstractBaseController { @RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public Object add(@Valid Member vo, BindingResult result) { // 增加前的准備操作路徑 if (result.hasErrors()) { // 現在表示執行的驗證出現錯誤 Iterator<ObjectError> iterator = result.getAllErrors().iterator(); // 獲取全部錯誤信息 while (iterator.hasNext()) { ObjectError error = iterator.next() ; // 取出每一個錯誤 System.out.println("【錯誤信息】code = " + error.getCode() + ",message = " + error.getDefaultMessage()); } return result.getAllErrors() ; } else { return vo; } } @RequestMapping(value = "/addPre", method = RequestMethod.GET) public String addPre() { // 增加前的准備操作路徑 return "member_add"; } }
對於此類的驗證大家理解即可,不需要將其作為重點,但是需要清楚,默認情況下 SpringBoot 提供的數據驗證需要通過注解 以及一系列的資源文件進行定義后才可以使用,而且所有的錯誤都必須用戶自己來處理,這一點的設計不如直接編寫具體的反射攔 截器方便。
2.2、處理錯誤頁
錯誤頁絕對是所有的 WEB 項目之中必須具有的一項信息顯示處理,但是在傳統的 WEB 項目開發過程之中,錯誤頁都是在 web.xml 文件之中進行配置的,不過遺憾的是 SpringBoot 之中並不存在有 web.xml 配置文件這一項,那么如果要想進行錯誤頁的處 理,最好的做法是需要根據每一個錯誤代碼創建一個屬於自己的錯誤顯示頁。
1、 所有的錯誤頁都是普通的靜態文件,那么建議在“src/main/view/static”目錄下創建幾個常見的錯誤頁(常見的錯誤的 HTTP 返回編碼:404、500、400)
2、 添加一個錯誤頁的配置類,在啟動類中編寫一個錯誤頁的配置項;
package cn.study.microboot.config; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @Configuration public class ErrorPageConfig { @Bean public EmbeddedServletContainerCustomizer containerCustomizer() { return new EmbeddedServletContainerCustomizer() { @Override public void customize( ConfigurableEmbeddedServletContainer container) { ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error-400.html"); ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-404.html"); ErrorPage errorPage500 = new ErrorPage( HttpStatus.INTERNAL_SERVER_ERROR, "/error-500.html"); container.addErrorPages(errorPage400, errorPage404, errorPage500); } }; } }
那么此時只要出現了錯誤,就會找到相應的 http 狀態碼,而后跳轉到指定的錯誤路徑上進行顯示。
2.3、全局異常
下面首先來觀察一個程序代碼,例如:現在建立一個控制器,而后這個控制器自己拋出一個異常。
@RequestMapping(value="/get") @ResponseBody public String get() { System.out.println("除法計算:" + (10 / 0)); return "hello world" ; }
如果此時配置有錯誤頁,那么這個時候錯誤會統一跳轉到 500 所在的路徑上進行錯誤的顯示,但是如果說現在希望能夠顯示 出錯誤更加詳細的內容呢?
所以這個時候可以單獨定義一個頁面進行錯誤的信息顯示處理,而這個頁面,可以定義在“src/main/view/templates/error.html”, 這個頁面里面要求可以輸出一些信息;
1、 定義一個全局的異常處理類:
import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice // 作為一個控制層的切面處理 public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; // 定義錯誤顯示頁,error.html @ExceptionHandler(Exception.class) // 所有的異常都是Exception子類 public ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) { // 出現異常之后會跳轉到此方法 ModelAndView mav = new ModelAndView(DEFAULT_ERROR_VIEW); // 設置跳轉路徑 mav.addObject("exception", e); // 將異常對象傳遞過去 mav.addObject("url", request.getRequestURL()); // 獲得請求的路徑 return mav; } }
2、 定義 error.html 頁面:
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>SpringBoot模版渲染</title> <link rel="icon" type="image/x-icon" href="/images/study.ico"/> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> </head> <body> <p th:text="${url}"/> <p th:text="${exception.message}"/> </body> </html>
對於全局異常信息顯示除了采用以上的跳轉處理方式之外,也可以做的簡單一些,使用 Rest 進行顯示。
范例:修改全局異常處理類
package cn.study.microboot.advice; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; //@ControllerAdvice// 作為一個控制層的切面處理 @RestControllerAdvice public class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; // 定義錯誤顯示頁,error.html @ExceptionHandler(Exception.class) // 所有的異常都是Exception子類 public Object defaultErrorHandler(HttpServletRequest request,Exception e) { class ErrorInfo { private Integer code ; private String message ; private String url ; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } ErrorInfo info = new ErrorInfo() ; info.setCode(100); // 標記一個錯誤信息類型 info.setMessage(e.getMessage()); info.setUrl(request.getRequestURL().toString()); return info ; } // public ModelAndView defaultErrorHandler(HttpServletRequest request, // Exception e) { // 出現異常之后會跳轉到此方法 // ModelAndView mav = new ModelAndView(DEFAULT_ERROR_VIEW); // 設置跳轉路徑 // mav.addObject("exception", e); // 將異常對象傳遞過去 // mav.addObject("url", request.getRequestURL()); // 獲得請求的路徑 // return mav; // } }
如果現在要想把異常的信息顯示的更加華麗一些(不是面對所有用戶),那么最好的做法就是使用全局異常處理的方式完成。