1. 程式人生 > >Spring boot 自定義異常頁面

Spring boot 自定義異常頁面

個人推薦第二種, 第一種不會正確列印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>