天天看點

【五】Spring Boot之 全局異常

一、一次請求分成三個階段,來分别進行全局的異常處理

第一階段:在進入Controller之前,譬如請求一個不存在的位址,404錯誤。 

第二階段:在執行@RequestMapping時,進入邏輯處理階段前。譬如傳的參數類型錯誤。 

第三階段:以上都正常時,在controller裡執行邏輯代碼時出的異常。譬如NullPointerException

二、SpringBoot内置異常 處理BasicErrorController

BasicErrorController提供兩種錯誤傳回

@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 ? new ModelAndView("error", model) : modelAndView);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
           

1.頁面請求發生錯誤則把請求轉到/error上。

頁面發生異常時會把請求轉到/error。這個/error路徑可以在application.yml上自定義

server:
  port: 8080
  error:
    path: /custom/error
           

2.json請求發生錯誤則會傳回json錯誤。

{
  "timestamp": 1478571808052,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/all"
}
           

三、自定義異常的兩種方案

1.使用@ControllerAdvice注解

Controller層的全局異常統一處理。這能處理上述第二、三階段的發生的異常。

無法根據不同的頭部傳回不同的資料格式,而且無法針對404、403等多種狀态進行處理。

@ControllerAdvice 主要是用來Controller的一些公共的需求的低侵入性增強提供輔助,作用于@RequestMapping标注的方法上。

換句話說就是,這裡能夠捕獲到的異常,是由前端調用controller層@RequestMapping标注的方法内産生的。

@ExceptionHandler(value = Exception.class)

該注解是配合@ExceptionHandler一起使用的注解,自定義錯誤處理器,可自己組裝json字元串,并傳回到頁面。

value中指定該方法需要處理的那些異常類。

如果傳回的是json,不是view,需要在方法上添加@ResponseBody

下圖代碼,是對Exception和自定義的BizException進行不同的處理,寫得比較粗糙。正常情況下,出于安全考慮,Exception資訊隻列印日志,不暴露給前端。

import common.request.spring.boot.autoconfigure.exception.BizException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;



/**
 * @program: 
 * @description: 全局異常 ControllerAdvice方式
 * @author: liyijie
 * @create: 2018-10-18 17:07
 **/
@Order(value = -1)
@ControllerAdvice //主要是用來Controller的一些公共的需求的低侵入性增強提供輔助,作用于@RequestMapping标注的方法上。
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 全局異常捕捉處理
     * @param e
     * @return
     */
    //該注解是配合@ExceptionHandler一起使用的注解,自定義錯誤處理器,可自己組裝json字元串,并傳回到頁面。
    //value中指定該方法需要處理的那些異常類
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public MyResponse<Void> exceptionHandler(Exception e) {
        logger.error(e.getMessage(),e);
        return new MyResponse<>(MyResponse.CODE_FAIL,null,e.toString(),System.currentTimeMillis());
    }

    /**
     * 攔截捕捉自定義異常 BizException.class
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = BizException.class)
    public MyResponse<Void> BizExceptionHandler(BizException e) {
        return new MyResponse<>(MyResponse.CODE_FAIL,null,e.getMsg(),System.currentTimeMillis());
    }
}
           

如果不起作用,請檢查兩個點:

1.看一下spring啟動類,看一下這個全局異常處理類,spring有沒有把它掃描進來!

2.看旗下spring啟動日志,是不是引的别的同僚的包或者用了什麼鬼東西,在加載自己的全局異常處理類之前就已經加載了别人的全局異常處理類了。解決方法:添加@order(value=-1),這樣就是先加載自己寫的這個全局異常處理類。

比如我就是這個問題。在加載我的之前就加載了另一個全局異常處理類exAdvice

2018-10-18 19:08:53.344 INFO [main]o.s.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.initExceptionHandlerAdviceCache:273 -Detected @ExceptionHandler methods in exAdvice
2018-10-18 19:08:53.345 INFO [main]o.s.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.initExceptionHandlerAdviceCache:273 -Detected @ExceptionHandler methods in globalExceptionHandler
           

2.實作ErrorController接口或者繼承AbstractErrorController抽象類或者繼承BasicErrorController類

示範實作ErrorController。這能處理上述第一階段發生的異常。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @description: 處理全局異常 請求進入@RequestMapping方法之前的異常
 * @create: 2018-10-18 20:34
 **/
@Controller
@EnableConfigurationProperties({ServerProperties.class})
public class ExceptionController implements ErrorController {

    private ErrorAttributes errorAttributes;
 
    @Autowired
    private ServerProperties serverProperties;
 
 
    /**
     * 初始化ExceptionController
     * @param errorAttributes
     */
    @Autowired
    public ExceptionController(ErrorAttributes errorAttributes) {
        Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
        this.errorAttributes = errorAttributes;
    }
 
 
    /**
     * 定義404的ModelAndView
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(produces = "text/html",value = "404")
    public ModelAndView errorHtml404(HttpServletRequest request,
                                  HttpServletResponse response) {
        response.setStatus(getStatus(request).value());
        Map<String, Object> model = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        return new ModelAndView("error/404", model);
    }
 
    /**
     * 定義404的JSON資料
     * @param request
     * @return
     */
    @RequestMapping(value = "404")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error404(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
 
    /**
     * 定義500的ModelAndView
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(produces = "text/html",value = "500")
    public ModelAndView errorHtml500(HttpServletRequest request,
                                  HttpServletResponse response) {
        response.setStatus(getStatus(request).value());
        Map<String, Object> model = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        return new ModelAndView("error/500", model);
    }
 
 
    /**
     * 定義500的錯誤JSON資訊
     * @param request
     * @return
     */
    @RequestMapping(value = "500")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
 
 
    /**
     * 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 = this.serverProperties.getError().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
 
 
    /**
     * 擷取錯誤的資訊
     * @param request
     * @param includeStackTrace
     * @return
     */
    private Map<String, Object> getErrorAttributes(HttpServletRequest request,
                                                   boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes,
                includeStackTrace);
    }
 
    /**
     * 是否包含trace
     * @param request
     * @return
     */
    private boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !"false".equals(parameter.toLowerCase());
    }
 
    /**
     * 擷取錯誤編碼
     * @param request
     * @return
     */
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request
                .getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        }
        catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }
 
    /**
     * 實作錯誤路徑,暫時無用
     * @see ExceptionMvcAutoConfiguration#containerCustomizer()
     * @return
     */
    @Override
    public String getErrorPath() {
        return "";
    }


}
           

或者另一種自定實作方式:繼承AbstractErrorController抽象類。

@Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
        private final ErrorProperties errorProperties;
        private static final Logger LOGGER = LoggerFactory.getLogger(BasicErrorController.class);
        @Autowired
        private ApplicationContext applicationContext;

        /**
         * Create a new {@link org.springframework.boot.autoconfigure.web.BasicErrorController} instance.
         *
         * @param errorAttributes the error attributes
         * @param errorProperties configuration properties
         */
        public BasicErrorController(ErrorAttributes errorAttributes,
                                    ErrorProperties errorProperties) {
            this(errorAttributes, errorProperties,
                    Collections.<ErrorViewResolver>emptyList());
        }

        /**
         * Create a new {@link org.springframework.boot.autoconfigure.web.BasicErrorController} instance.
         *
         * @param errorAttributes    the error attributes
         * @param errorProperties    configuration properties
         * @param errorViewResolvers error view resolvers
         */
        public BasicErrorController(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);
            insertError(request);
            return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
        }



        @RequestMapping
        @ResponseBody
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
            Map<String, Object> body = getErrorAttributes(request,
                    isIncludeStackTrace(request, MediaType.ALL));
            HttpStatus status = getStatus(request);
            insertError(request);
            return new ResponseEntity(body, status);
        }

        /**
         * 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;
        }
    }
           

繼續閱讀