Spring boot 自定義異常頁面
阿新 • • 發佈:2018-11-07
個人推薦第二種, 第一種不會正確列印url
第一種:
package com.springboot.common.controller; import com.springboot.common.enumerate.HttpStatusEnum; import com.springboot.common.pojo.Error; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; /** * 如果使用GlobalExceptionHandler就註釋掉該類 * @Auther: quleou * @Date: 2018/8/28 13:41 * @Description: */ //@Controller //使用請開啟註釋 public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController { private static final String DEFAULT_ERROR_MESSAGE = "服務繁忙,請稍後再試!"; private static final String errorDir = "error/"; @Override public String getErrorPath() { return "default"; } @RequestMapping(value = "/error") public String error(Model model, HttpServletRequest request, HttpServletResponse response, Exception e) { // 自定義的Error Error error = new Error(); // 自定義Http狀態碼列舉 HttpStatusEnum httpStatusEnum = HttpStatusEnum.valueOf(response.getStatus()); // 異常資訊 error.setMessage(getErrorMessage(httpStatusEnum)); // 異常url error.setUrl(new String(request.getRequestURL())); // 異常時間 error.setTimestamp( new Date().toString()); // 異常狀態碼 error.setStatus(httpStatusEnum.code()); model.addAttribute("error", error); String errorPath = getErrorPath(); // 處理跳轉頁面 switch (httpStatusEnum.code()) { case 404: errorPath = errorDir + "404"; break; case 500: errorPath = errorDir + "500"; break; default: errorPath = errorDir + errorPath; } return errorPath; } // 根據狀態碼獲得返回訊息 public static String getErrorMessage(HttpStatusEnum httpStatusEnum) { if (httpStatusEnum == null) return DEFAULT_ERROR_MESSAGE; return httpStatusEnum.reasonPhraseUS() + " " + httpStatusEnum.reasonPhraseCN(); } }
第二種:
BaseGlobalExceptionHandler 父類
package com.springboot.common.handler; import com.google.common.base.Throwables; import com.springboot.common.enumerate.HttpStatusEnum; import com.springboot.common.pojo.Error; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; /** * @Auther: quleou * @Date: 2018/8/28 10:20 * @Description: */ public class BaseGlobalExceptionHandler { protected static final Logger logger = null; protected static final String DEFAULT_ERROR_MESSAGE = "服務繁忙,請稍後再試!"; private static final String errorDir = "error/"; protected ModelAndView handleError(HttpServletRequest request, HttpServletResponse response, Exception e, String viewName, HttpStatus status) throws Exception { if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) throw e; HttpStatusEnum httpStatusEnum = HttpStatusEnum.valueOf(status.value()); // String errorMsg = e instanceof MessageException ? e.getMessage() : DEFAULT_ERROR_MESSAGE; // String errorMsg = DEFAULT_ERROR_MESSAGE; String errorStack = Throwables.getStackTraceAsString(e); getLogger().error("Request: {} raised {}", request.getRequestURI(), errorStack); if (isAjaxRequest(request)) { return handleAjaxError(request, response, httpStatusEnum); } return handleViewError(request, response, request.getRequestURL().toString(), errorStack, viewName, httpStatusEnum); } // 處理web異常 protected ModelAndView handleViewError(HttpServletRequest request, HttpServletResponse response,String url, String errorStack, String viewName, HttpStatusEnum httpStatusEnum) { ModelAndView mav = new ModelAndView(); Error error = new Error(); error.setMessage(getErrorMessage(httpStatusEnum)); error.setUrl(new String(request.getRequestURL())); error.setTimestamp( new Date().toString()); error.setStatus(httpStatusEnum.code()); mav.addObject("error", error); mav.setViewName(errorDir + viewName); return mav; } // 處理ajax異常 protected ModelAndView handleAjaxError(HttpServletRequest request, HttpServletResponse response, HttpStatusEnum httpStatusEnum) throws IOException { String errorMessage = getErrorMessage(httpStatusEnum); response.setCharacterEncoding("UTF-8"); response.setStatus(httpStatusEnum.code()); PrintWriter writer = response.getWriter(); writer.write(errorMessage); writer.flush(); return null; } // 判斷是否是Ajax請求 public boolean isAjaxRequest(HttpServletRequest request){ String header = request.getHeader("X-Requested-With"); boolean isAjax = "XMLHttpRequest".equals(header) ? true:false; return isAjax; } // 根據狀態碼獲得返回訊息 public static String getErrorMessage(HttpStatusEnum httpStatusEnum) { if (httpStatusEnum == null) return DEFAULT_ERROR_MESSAGE; return httpStatusEnum.reasonPhraseUS() + " " + httpStatusEnum.reasonPhraseCN(); } public Logger getLogger() { return LoggerFactory.getLogger(BaseGlobalExceptionHandler.class); } }
GlobalExceptionHandler 處理類
package com.springboot.common.handler; import org.springframework.http.HttpStatus; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 如果使用ErrorController就註釋掉ControllerAdvice * @Auther: quleou * @Date: 2018/8/27 22:51 * @Description: */ @ControllerAdvice public class GlobalExceptionHandler extends BaseGlobalExceptionHandler{ // 404 異常 @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(value = HttpStatus.NOT_FOUND) public ModelAndView handle404Error(HttpServletRequest request, HttpServletResponse response, Exception e)throws Exception { return handleError(request, response, e, "404", HttpStatus.NOT_FOUND); } // 405 異常 @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseStatus(value = HttpStatus.METHOD_NOT_ALLOWED) public ModelAndView handle405Error(HttpServletRequest request, HttpServletResponse response, Exception e)throws Exception { return handleError(request, response, e, "default", HttpStatus.METHOD_NOT_ALLOWED); } // 500異常 @ExceptionHandler(value = Exception.class) public ModelAndView handleError(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception { return handleError(request, response, e, "default", null); // return "error/404"; } }
其中使用到自定義類
Error異常類
package com.springboot.common.pojo;
import java.io.Serializable;
/**
* @Auther: Lenovo006
* @Date: 2018/8/28 15:33
* @Description:
*/
public class Error implements Serializable{
private int status; // 錯誤狀態碼
private String message; // 錯誤資訊
private String url; // 錯誤url
private String timestamp;// 時間
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
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;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
Http狀態碼 HttpStatusEnum
package com.springboot.common.enumerate;
import org.springframework.http.HttpStatus;
/**
* Http狀態碼列舉
*/
public enum HttpStatusEnum {
CONTINUE(100, "Continue", "請繼續傳送請求的剩餘部分"),
SWITCHING_PROTOCOLS(101, "Switching Protocols", "協議切換"),
PROCESSING(102, "Processing", "請求將繼續執行"),
// for 103 https://news.ycombinator.com/item?id=15590049
CHECKPOINT(103, "Checkpoint", "可以預載入"),
OK(200, "OK", "請求已經成功處理"),
CREATED(201, "Created", "請求已經成功處理,並建立了資源"),
ACCEPTED(202, "Accepted", "請求已經接受,等待執行"),
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information", "請求已經成功處理,但是資訊不是原始的"),
NO_CONTENT(204, "No Content", "請求已經成功處理,沒有內容需要返回"),
RESET_CONTENT(205, "Reset Content", "請求已經成功處理,請重置檢視"),
PARTIAL_CONTENT(206, "Partial Content", "部分Get請求已經成功處理"),
MULTI_STATUS(207, "Multi-Status", "請求已經成功處理,將返回XML訊息體"),
ALREADY_REPORTED(208, "Already Reported", "請求已經成功處理,一個DAV的繫結成員被前一個請求列舉,並且沒有被再一次包括"),
IM_USED(226, "IM Used", "請求已經成功處理,將響應一個或者多個例項"),
MULTIPLE_CHOICES(300, "Multiple Choices", "提供可供選擇的回饋"),
MOVED_PERMANENTLY(301, "Moved Permanently", "請求的資源已經永久轉移"),
FOUND(302, "Found", "請重新發送請求"),
// MOVED_TEMPORARILY(302, "Moved Temporarily", "") 已經過時
SEE_OTHER(303, "See Other", "請以Get方式請求另一個URI"),
NOT_MODIFIED(304, "Not Modified", "資源未改變"),
USE_PROXY(305, "Use Proxy", "請通過Location域中的代理進行訪問"),
// 306在新版本的規範中被棄用
TEMPORARY_REDIRECT(307, "Temporary Redirect", "請求的資源臨時從不同的URI響應請求"),
RESUME_INCOMPLETE(308, "Resume Incomplete", "請求的資源已經永久轉移"),
BAD_REQUEST(400, "Bad Request", "請求錯誤,請修正請求"),
UNAUTHORIZED(401, "Unauthorized", "沒有被授權或者授權已經失效"),
PAYMENT_REQUIRED(402, "Payment Required", "預留狀態"),
FORBIDDEN(403, "Forbidden", "請求被理解,但是拒絕執行"),
NOT_FOUND(404, "Not Found", "資源未找到"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed", "請求方法不允許被執行"),
NOT_ACCEPTABLE(406, "Not Acceptable", "請求的資源不滿足請求者要求"),
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required", "請通過代理進行身份驗證"),
REQUEST_TIMEOUT(408, "Request Timeout", "請求超時"),
CONFLICT(409, "Conflict", "請求衝突"),
GONE(410, "Gone", "請求的資源不可用"),
LENGTH_REQUIRED(411, "Length Required", "Content-Length未定義"),
PRECONDITION_FAILED(412, "Precondition Failed", "不滿足請求的先決條件"),
REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large", "請求傳送的實體太大"),
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long", "請求的URI超長"),
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type", "請求傳送的實體型別不受支援"),
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable", "Range指定的範圍與當前資源可用範圍不一致"),
EXPECTATION_FAILED(417, "Expectation Failed", "請求頭Expect中指定的預期內容無法被伺服器滿足"),
// I_AM_A_TEAPOT(418, "I'm a teapot", ""), 該程式碼沒有被伺服器實現
// INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource", ""), 已經過時
// METHOD_FAILURE(420, "Method Failure", ""), 已經過時
// DESTINATION_LOCKED(421, "Destination Locked", ""), 已經過時
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity", "請求格式正確,但是由於含有語義錯誤,無法響應"),
LOCKED(423, "Locked", "當前資源被鎖定"),
FAILED_DEPENDENCY(424, "Failed Dependency", "由於之前的請求發生錯誤,導致當前請求失敗"),
UPGRADE_REQUIRED(426, "Upgrade Required", "客戶端需要切換到TLS1.0"),
PRECONDITION_REQUIRED(428, "Precondition Required", "請求需要提供前置條件"),
TOO_MANY_REQUESTS(429, "Too Many Requests", "請求過多"),
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large", "請求頭超大,拒絕請求"),
INTERNAL_SERVER_ERROR(500, "Internal Server Error", "伺服器內部錯誤"),
NOT_IMPLEMENTED(501, "Not Implemented", "伺服器不支援當前請求的部分功能"),
BAD_GATEWAY(502, "Bad Gateway", "響應無效"),
SERVICE_UNAVAILABLE(503, "Service Unavailable", "伺服器維護或者過載,拒絕服務"),
GATEWAY_TIMEOUT(504, "Gateway Timeout", "上游伺服器超時"),
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported", "不支援的HTTP版本"),
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates", "伺服器內部配置錯誤"),
INSUFFICIENT_STORAGE(507, "Insufficient Storage", "伺服器無法完成儲存請求所需的內容"),
LOOP_DETECTED(508, "Loop Detected", "伺服器處理請求時發現死迴圈"),
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded", "伺服器達到頻寬限制"),
NOT_EXTENDED(510, "Not Extended", "獲取資源所需的策略沒有被滿足"),
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required", "需要進行網路授權");
private final int code; // 狀態碼
private final String reasonPhraseUS;// 英文解析
private final String reasonPhraseCN;// 中文解析
private static final int
INFORMATIONAL = 1,
SUCCESSFUL = 2,
REDIRECTION = 3,
CLIENT_ERROR = 4,
SERVER_ERROR = 5;
HttpStatusEnum(int code, String reasonPhraseUS, String reasonPhraseCN) {
this.code = code;
this.reasonPhraseUS = reasonPhraseUS;
this.reasonPhraseCN = reasonPhraseCN;
}
public int code() {
return code;
}
public String reasonPhraseUS() {
return reasonPhraseUS;
}
public String reasonPhraseCN() {
return reasonPhraseCN;
}
public static HttpStatusEnum valueOf(int code) {
for (HttpStatusEnum httpStatus : values()) {
if (httpStatus.code() == code) {
return httpStatus;
}
}
throw new IllegalArgumentException("No matching constant for [" + code + "]");
}
// 1xx 響應資訊提示
public boolean is1xxInformational() {
return type() == INFORMATIONAL;
}
// 2xx 成功
public boolean is2xxSuccessful() {
return type() == SUCCESSFUL;
}
// 3xx 重定向
public boolean is3xxRedirection() {
return type() == REDIRECTION;
}
// 4xx 客戶端錯誤
public boolean is4xxClientError() {
return type() == CLIENT_ERROR;
}
// 5xx 服務端錯誤
public boolean is5xxServerError() {
return type() == SERVER_ERROR;
}
private int type(){
return (int) code / 100;
}
}
其中的html模板,可根據需求自行更改 其中無論404 405 500 default都是以下模板
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title>Error</title>
</head>
<body>
<h1 th:text="${error.status}"></h1>
<h1 th:text="${error.url}"></h1>
<h1 th:text="${error.message}"></h1>
<h1 th:text="${error.timestamp}"></h1>
</body>
</html>