SpringBoot優雅的全局異常處理


前言

在日常項目開發中,異常是常見的,但是如何更高效的處理好異常信息,讓我們能快速定位到BUG,是很重要的,不僅能夠提高我們的開發效率,還能讓你代碼看上去更舒服,SpringBoot的項目已經有一定的異常處理了,但是對於我們開發者而言可能就不太合適了,因此我們需要對這些異常進行統一的捕獲並處理。

SpringBoot默認的錯誤處理機制

返回錯誤頁面

默認返回 Whitelabel Error Page頁面的樣式太單調,用戶體驗不好。

如 果 我 們 需 要 將 所 有 的 異 常 同 一 跳 轉 到 自 定 義 的 錯 誤 頁 面 , 需 要 再 src/main/resources/templates 目錄下創建 error.html 頁面。

注意:名稱必須叫 error

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
        <!--SpringBoot默認存儲異常信息的key為exception-->
	<span th:text="${exception}" />
</body>
</html>

返回json格式api

Json格式的結果字符串不統一,與前端人員約定統一格式不一致

源碼分析

SpringBoot在頁面 發生異常的時候會自動把請求轉到/error,SpringBoot內置了一個BasicErrorController對異常進行統一的處理,當然也可以自定義這個路徑

@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.getErrorAttributeOptions(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) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

我們可以看到剛好對照兩個方法一個返回錯誤頁面,一個返回錯誤字符,默認錯誤路徑是/error如果有自定義就用自定義的

server.error.path=/custom/error

自定義錯誤處理

SpringBoot提供了ErrorAttribute類型
自定義ErrorAttribute類型的bean還是默認的兩種響應方式,只不過改變了響應內容項而已

package cn.soboys.core;


import cn.hutool.core.bean.BeanUtil;
import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultCode;
import cn.soboys.core.ret.ResultResponse;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/18 14:14
 * 全局錯誤
 */
@Component
public class GlobalErrorHandler extends DefaultErrorAttributes {


    /**
     * 自定義錯誤返回頁面
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        return super.resolveException(request, response, handler, ex);
    }

    /**
     * 自定義錯誤返回格式
     *
     * @param webRequest
     * @param options
     * @return
     */
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        Result result = ResultResponse.failure(ResultCode.NOT_FOUND, errorAttributes.get("path"));
        Map map = BeanUtil.beanToMap(result, true, true);
        return map;
    }
}

自定義業務異常類

繼承RuntimeException

package cn.soboys.core.authentication;

import cn.soboys.core.ret.ResultCode;
import lombok.Data;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/22 13:58
 * 認證異常
 */
@Data
public class AuthenticationException extends RuntimeException {

    public AuthenticationException(String message) {
        super(message);
    }

}

全局捕獲異常

通過SpringBoot提供的@RestControllerAdvice@ControllerAdvice 結合@ExceptionHandler使用

@RestControllerAdvice@ControllerAdvice區別和@RestController,@Controller一樣如果想返回json格式也可以單獨使用@ResponseBody注解在方法上

需要捕獲什么異常通過@ExceptionHandler來指定對應異常類就可以了這里原則是按照從小到大異常進行依次執行

通俗來講就是當小的異常沒有指定捕獲時,大的異常包含了此異常就會被執行比如Exception 異常包含了所有異常類,是所有異常超級父類,當出現沒有指定異常時此時對應捕獲了Exception異常的方法會執行

@ExceptionHandler注解處理異常

@Controller
public class DemoController {
	@RequestMapping("/show")
	public String showInfo() {
		String str = null;
		str.length();
		return "index";
	}

	@RequestMapping("/show2")
	public String showInfo2() {
		int a = 10 / 0;
		return "index";
	}

	/**
	 * java.lang.ArithmeticException 該方法需要返回一個 ModelAndView:目的是可以讓我們封裝異常信息以及視
	 * 圖的指定 參數 Exception e:會將產生異常對象注入到方法中
	 */
	@ExceptionHandler(value = { java.lang.ArithmeticException.class })
	public ModelAndView arithmeticExceptionHandler(Exception e) {
		ModelAndView mv = new ModelAndView();
		mv.addObject("error", e.toString());
		mv.setViewName("error1");
		return mv;
	}

	/**
	 * java.lang.NullPointerException 該方法需要返回一個 ModelAndView:目的是可以讓我們封裝異常信息以及視
	 * 圖的指定 參數 Exception e:會將產生異常對象注入到方法中
	 */
	@ExceptionHandler(value = { java.lang.NullPointerException.class })
	public ModelAndView nullPointerExceptionHandler(Exception e) {
		ModelAndView mv = new ModelAndView();
		mv.addObject("error", e.toString());
		mv.setViewName("error2");
		return mv;
	}
}

優點:可以自定義異常信息存儲的key,自定義跳轉視圖的名稱

缺點:需要編寫大量的異常處理方法,不能跨controller,如果兩個controller中出現同樣的異常,需要重新編寫異常處理的方法

@ControllerAdvice+@ExceptionHandler 注解處理異常

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/17 20:19
 * 全局異常統一處理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
 /**
     * 認證異常
     * @param e
     * @return
     */
    @ExceptionHandler(AuthenticationException.class)
    public Result UnNoException(AuthenticationException e) {
        return ResultResponse.failure(ResultCode.UNAUTHORIZED,e.getMessage());
    }

    /**
     *
     * @param e 未知異常捕獲
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result UnNoException(Exception e) {
        return ResultResponse.failure(ResultCode.INTERNAL_SERVER_ERROR, e.getMessage());
    }
}

優點:可以自定義異常信息存儲的key,自定義跳轉視圖的名稱,跨controller統一攔截統一捕獲,一般都是使用這種


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM