1. 程式人生 > >springboot自定義404,415錯誤處理(親測可用,易懂)

springboot自定義404,415錯誤處理(親測可用,易懂)

  • 最近做一個專案,專案絕大多數請求都用的是ajax請求,但是,如果是4XX錯誤的話,springboot返回它自己的一套json,(全域性異常處理是捕獲不到這種錯誤的)如下:
{
  "timestamp": 1538032849685,
  "status": 415,
  "error": "Unsupported Media Type",
  "message": "Content type 'application/x-www-form-urlencoded' not supported",
  "path": "/account/login"
}
  • 我們知道,做rest api,我們的資料格式是統一的,而springboot給的我們如此格式,肯定不是我們想要的,我查了好多資料,發現說法雲的霧的,好多純屬誤導,我不知道他們寫的時候,親測過沒有,真實放在專案中沒有!
  • 所以決定自己動手造輪子,追尋原始碼,我們會發現,上述json串是springboot裡面一個BasicErrorController中的error方法返回的,而分析它的兩個方法,一個error,一個errorHtml已經覆蓋了所有的請求型別,因為你的請求要麼是瀏覽器請求,要麼不是,你想繼承BasicErrorController複寫方法?人家已經定義好返回型別,你怎麼複寫?然而,查資料我們會發現,如果有其他的ErrorController的實現,則預設的BasicErrorController將失去效果,如此,springboot就將這個錯誤處理的權利完全交給了程式猿,我們可以根據我們的具體需求,實現我們自己的ErrorController,其實BasicErrorController從某種意義上來說,是springboot提供給使用者的一個例子,告訴使用者,如果你想處理錯誤,應該怎麼做!所以就自己處理了一個ErrorController,親測完全可用,程式碼如下:
package com.rjhcsoft.credit.controller;

import com.rjhcsoft.credit.exceptions.BaseResponse;
import com.rjhcsoft.credit.exceptions.Status;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public ErrorController(){
        this(new DefaultErrorAttributes(),new ErrorProperties());
    }

    /**
     * Create a new {@link ErrorController} instance.
     * @param errorAttributes the error attributes
     * @param errorProperties configuration properties
     */
    public ErrorController(ErrorAttributes errorAttributes,
                                ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

    /**
     * Create a new {@link ErrorController} instance.
     * @param errorAttributes the error attributes
     * @param errorProperties configuration properties
     * @param errorViewResolvers error view resolvers
     */
    public ErrorController(ErrorAttributes errorAttributes,
                                ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    @Override
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
                                  HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
    }

    @RequestMapping
    @ResponseBody
    public BaseResponse error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        Status sta = null;
        if (status.is1xxInformational()){
            sta=Status.INFORMATION_EXCEPTION;
        }else if (status.is3xxRedirection()){
            sta=Status.REDIRECT_ERROR;
        }else if (status.is4xxClientError()){
            sta=Status.CLIENT_ERROR;
        }else if (status.is5xxServerError()){
            sta=Status.SERVER_ERROR;
        }else sta=Status.UNKNKOWN_REQUEST;
        return BaseResponse.error(sta,body);
    }

    /**
     * Determine if the stacktrace attribute should be included.
     * @param request the source request
     * @param produces the media type produced (or {@code MediaType.ALL})
     * @return if the stacktrace attribute should be included
     */
    protected boolean isIncludeStackTrace(HttpServletRequest request,
                                          MediaType produces) {
        ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    /**
     * Provide access to the error properties.
     * @return the error properties
     */
    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

}

  • 注意:其中的error方法的返回值BaseResponse是我自己定義的一套ajax返回格式,具體情況具體對待,其中Status是我們自己定義的一套狀態碼,是列舉,這個大家也是具體情況具體對待
  • 最後的返回結果如下:
{
  "code": "1005",
  "data": {
    "timestamp": 1538033789395,
    "status": 415,
    "error": "Unsupported Media Type",
    "message": "Content type 'application/x-www-form-urlencoded' not supported",
    "path": "/account/login"
  },
  "msg": "客戶端請求錯誤,4XX錯誤"
}
  • 大家發現,最後的返回結果已經是我們自己定義的格式了,至於全域性異常處理網上有很多,這裡我也貼一份我定義的
package com.rjhcsoft.credit.exceptions.resolve;

import com.alibaba.fastjson.JSON;
import com.rjhcsoft.credit.exceptions.BaseException;
import com.rjhcsoft.credit.exceptions.BaseResponse;
import com.rjhcsoft.credit.exceptions.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class GlobalExceptionResolve implements HandlerExceptionResolver {
    private static final Logger EXCEPTION = LoggerFactory.getLogger("exception");

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        response.setStatus(200);
        BaseResponse baseResponse = parseException(ex);
        if (isAjax(request)) return returnJson(baseResponse,response);
        else return returnView(baseResponse,response);
    }

    /**
     * 解析異常,這裡做簡單解析
     * @param ex
     * @return
     */
    private BaseResponse parseException(Exception ex){
        EXCEPTION.error(ex.getMessage());
        if (ex instanceof BaseException){
            Exception e = ((BaseException) ex).getE();
            EXCEPTION.error(e==null?"":e.getMessage()==null?"":e.getMessage());
            return BaseResponse.error(((BaseException) ex).getStatus());
        }else return BaseResponse.error(Status.UNKNKOWN);
    }

    /**
     * 返回json串
     * @param baseResponse
     * @param response
     * @return
     */
    public static ModelAndView returnJson(BaseResponse baseResponse,HttpServletResponse response){
        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        try(PrintWriter writer = response.getWriter()) {
            writer.print(JSON.toJSONString(baseResponse));
        }catch (IOException e){
            e.printStackTrace();
        }
        return new ModelAndView();
    }

    /**
     * 返回頁面
     * @param baseResponse
     * @param response
     * @return
     */
    public static ModelAndView returnView(BaseResponse baseResponse,HttpServletResponse response){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/error");
        modelAndView.addObject("response",baseResponse);
        return modelAndView;
    }

    /**
     * 判定師傅是ajax請求
     * @param request
     * @return
     */
    public static boolean isAjax(HttpServletRequest request){
        String requestType = request.getHeader("X-Requested-With");
        if("XMLHttpRequest".equals(requestType)){
            return true;
        }else{
            return false;
        }
    }
}
  • BaseException
package com.rjhcsoft.credit.exceptions;

public class BaseException extends RuntimeException{
    private Status status;
    private Exception e;
    protected BaseException(Status status){
        super(status.json());
        this.status=status;
    }

    protected BaseException(Status status,Exception e){
        this(status);
        this.e = e;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public Exception getE() {
        return e;
    }

    public void setE(Exception e) {
        this.e = e;
    }
}