spring boot異常處理

spring boot異常處理

spring mvc異常處理一文中,我介紹了在spring mvc中如何配置異常處理鏈、如何捕獲異常、如何構(gòu)建統(tǒng)一的異常處理。今天來(lái)談?wù)勅绾卧趕pring boot配置異常的統(tǒng)一處理方式。

spring boot和spring mvc中異常處理的不同

在spring mvc中,如果最終有未處理的異常,DispatcherServlet將會(huì)重新發(fā)送/error請(qǐng)求用于返回最終的錯(cuò)誤信息,但是Servlet本身并不提供全局的錯(cuò)誤頁(yè)面,而是需要開發(fā)者在web.xml中配置:

<error-page> 
    <location>/error</location> 
</error-page>

也就是說(shuō),即便是采用Spring中的Bean配置方式一文中介紹的Java注解的方式實(shí)現(xiàn)無(wú)xml的系統(tǒng)配置,仍然需要開發(fā)者提供微型的web.xml配置文件來(lái)實(shí)現(xiàn)全局錯(cuò)誤文件的配置。

而在spring boot中,當(dāng)最終有未處理的異常拋出的時(shí)候,Servlet容器仍然會(huì)發(fā)送/error請(qǐng)求,但是和spring mvc不同的是,spring boot提供了內(nèi)置的BasicErrorController處理全局的錯(cuò)誤信息,不需要任何其他的配置。

下面通過一個(gè)簡(jiǎn)單的例子驗(yàn)證一下spring boot中默認(rèn)的異常處理流程:

  1. 首先在HomeController中映射index請(qǐng)求,接口中什么都不做,僅拋出一個(gè)RuntimeException異常。

    @Controller
    public class HomeController {
        @GetMapping(value = "/index")
        @ResponseBody
        public String index() {
            throw new RuntimeException("runtime exception in /index");
        }
    }
    
  2. 然后請(qǐng)求該接口,結(jié)果如下:

1.jpg

在這里,處理在一個(gè)請(qǐng)求接口中排出了異常,我們沒有做其他任何操作,spring boot自動(dòng)的給我們展示了這個(gè)whitelabel錯(cuò)誤信息。那么這是怎么實(shí)現(xiàn)的呢?

BasicErrorController完成默認(rèn)的異常處理

實(shí)際上,spring boot已經(jīng)為我們提供了/error請(qǐng)求的controller,它就是BasicErrorController

BasicErrorController的源碼如下:

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    // ... 省略構(gòu)造函數(shù)
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

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

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

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
    
    // ... 省略其他方法
}

從源碼可知:

  • BasicErrorController處理${server.error.path:${error.path:/error}請(qǐng)求。意思是:

    • 如果在application.properties中設(shè)置了server.error.path,就映射該值;
    • 否則,如果error.path有值就映射該值
    • 否則映射/error

    可以通過修改server.error.patherror.pathBasicErrorController不再處理error請(qǐng)求

  • BasicErrorControllererrorHtmlerror兩種不同的處理接口處理請(qǐng)求,其中errorHtml特指http請(qǐng)求中accept屬性值為text/html的請(qǐng)求。

    如果請(qǐng)求的返回類型不同,可以為一個(gè)請(qǐng)求通過設(shè)置produces指定特定的返回類型

自定義錯(cuò)誤頁(yè)面

spring boot默認(rèn)的錯(cuò)誤頁(yè)面顯然不能滿足開發(fā)的正常需求,通過在src/main/resources/templates文件夾中添加error.ftl(基于freemaker模板)錯(cuò)誤頁(yè)面實(shí)現(xiàn)自定義錯(cuò)誤信息。還可以通過在src/main/resources/templates/error中添加404.ft l等以http錯(cuò)誤碼開頭的頁(yè)面實(shí)現(xiàn)不同http錯(cuò)誤狀態(tài)的不同展現(xiàn)。結(jié)構(gòu)如下圖:

2.jpg

如果error文件夾下有對(duì)應(yīng)的狀態(tài)碼錯(cuò)誤頁(yè)面,則會(huì)渲染該頁(yè)面;否則,渲染error.flt頁(yè)面。

具體的錯(cuò)誤頁(yè)面的內(nèi)容,大家根據(jù)自己項(xiàng)目的情況,設(shè)計(jì)自己的展示樣式。

統(tǒng)一異常處理

前文說(shuō)過,/error請(qǐng)求的觸發(fā)前提是系統(tǒng)中拋出的異常到最終都沒有被處理掉,在spring mvc異常處理中提到可以通過@ControllerAdvice和@ExceptionHandler實(shí)現(xiàn)捕獲系統(tǒng)中的異常,在spring boot中該方法同樣奏效。需要注意的是,如果@ControllerAdvice中如果有其他異常沒有捕獲到,最終仍然會(huì)通過BasicErrorController處理這些異常。

統(tǒng)一異常處理部分代碼如下:

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 接口參數(shù)異常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {InterfaceIllegalArgumentException.class})
    public Map<String, Object> handleIllegalArgumentException(InterfaceIllegalArgumentException e) {
        LOGGER.error(e.getMessage(), e);
        return OutPut.failure(HttpStatusWrapper.ILLEGAL_REQUEST_PARAMETERS, e.getMessage());
    }


    /**
     * 其他未知異常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {Exception.class})
    public Map<String, Object> handleException(Exception e) {
        LOGGER.error(e.getMessage(), e);
        return OutPut.failure(HttpStatusWrapper.INTERNAL_SERVER_ERROR, ResponseMsg.QUERY_FAILURE);
    }

    /**
     * 請(qǐng)求闡述不匹配錯(cuò)誤
     * @param ex
     * @param headers
     * @param status
     * @param request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return ResponseEntity.status(HttpStatusWrapper.REQUEST_PARAMETER_TYPE_MISMATCH.getCode()).body(OutPut.failure(HttpStatusWrapper.REQUEST_PARAMETER_TYPE_MISMATCH, ex.getValue() + "的類型不匹配,需要" + ex.getRequiredType()));
    }

    /**
     * 請(qǐng)求的類型不支持
     * @param ex
     * @param headers
     * @param status
     * @param request
     * @return
     */
    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        final String supportMediaTypes = ex.getSupportedMediaTypes().stream()
                .map(MimeType::getType)
                .collect(Collectors.joining(","));
        return ResponseEntity.status(HttpStatusWrapper.UNSUPPORTED_MEDIA_TYPE.getCode()).body(OutPut.failure(HttpStatusWrapper.UNSUPPORTED_MEDIA_TYPE, ex.getContentType() + "is not supported, the support media type are" + String.join(",", supportMediaTypes)));
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,680評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,283評(píng)論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,966評(píng)論 1 92
  • 原文鏈接:https://docs.spring.io/spring-boot/docs/1.4.x/refere...
    pseudo_niaonao閱讀 4,900評(píng)論 0 9
  • 項(xiàng)目難免會(huì)出現(xiàn)系統(tǒng)拋出異?;蛘?04 。 直接把錯(cuò)誤信息反饋給用戶不太好。所以要統(tǒng)一處理異常并返回直觀的提示。【以...
    else05閱讀 2,142評(píng)論 2 1

友情鏈接更多精彩內(nèi)容