1. 程式人生 > >spring-boot-全域性異常

spring-boot-全域性異常

Spring Boot預設的異常處理機制

預設情況下,Spring Boot為兩種情況提供了不同的響應方式。

一種是瀏覽器客戶端請求一個不存在的頁面或服務端處理髮生異常時,一般情況下瀏覽器預設傳送的請求頭中Accept: text/html,所以Spring Boot預設會響應一個html文件內容,稱作“Whitelabel Error Page”。

另一種是使用Postman等除錯工具傳送請求一個不存在的url或服務端處理髮生異常時,Spring Boot會返回類似如下的Json格式字串資訊

{
    "timestamp": "2018-05-12T06:11:45.209+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/index.html"
}

Spring Boot 預設提供了程式出錯的結果對映路徑/error。這個/error請求會在BasicErrorController中處理,其內部是通過判斷請求頭中的Accept的內容是否為text/html來區分請求是來自客戶端瀏覽器(瀏覽器通常預設自動傳送請求頭內容Accept:text/html)還是客戶端介面的呼叫,以此來決定返回頁面檢視還是 JSON 訊息內容

如何自定義錯誤頁面

  • 在/resources/templates下面建立error.html就可以覆蓋預設的Whitelabel Error Page的錯誤頁面

  • 訪問不存在的頁面時,結果如下:

  • 根據不同的狀態碼返回不同的檢視頁面,也就是對應的404,500等頁面
    • 靜態的:如果只是靜態HTML頁面,不帶錯誤資訊的,在resources/public/下面建立error目錄,在error目錄下面建立對應的狀態碼html即可

    • 如果是動態模板頁面,可以帶上錯誤資訊,在resources/templates/下面建立error目錄

  • 模擬下500錯誤
@Controller 
public class BaseErrorController extends  AbstractController{ 
private Logger logger = LoggerFactory.getLogger(this.getClass()); 

    @RequestMapping(value="/ex") 
    @ResponseBody 
    public String error(){ 
        int i=5/0; 
        return "ex"; 
    } 
}

如果同時存在靜態頁面500.html和動態模板的500.html,則後者覆蓋前者。即templates/error/這個的優先順序比resources/public/error高。
error.html會覆蓋預設的 whitelabel Error Page 錯誤提示, 靜態錯誤頁面優先級別比error.html高

根據不同的狀態碼,自動轉發到對應的錯誤的頁面上

@Configuration
public class ContainerConfig {
    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                //如果發生500錯誤,就會將請求轉發到/error/500這個對映來
                container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
                //如果發生404錯誤,就會將請求轉發到/error/404這個對映來
                container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
            }
        };
    }
}


@Controller
public class MyBasicErrorController extends BasicErrorController {

    public MyBasicErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    /**
     * 定義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));
        model.put("msg","自定義錯誤資訊");
        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);
    }


    /**
     * 定義404的ModelAndView
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(produces = "text/html",value = "/404")
    public ModelAndView errorHtml400(HttpServletRequest request,
                                     HttpServletResponse response) {
        response.setStatus(getStatus(request).value());
        Map<String, Object> model = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("msg","自定義錯誤資訊");
        return new ModelAndView("error/404", model);
    }
}

BasicErrorController預設對應的@RequestMapping是/error,固我們方法裡面對應的@RequestMapping(produces = "text/html",value = "/500")實際上完整的對映請求是/error/500,這就跟上面 customize 方法自定義的對映路徑對上了

通過@ControllerAdvice註解來處理異常

Spring Boot提供的ErrorController是一種全域性性的容錯機制。此外,你還可以用@ControllerAdvice註解和@ExceptionHandler註解實現對指定異常的特殊處理。

這裡介紹兩種情況:

  • 區域性異常處理 @Controller + @ExceptionHandler
  • 全域性異常處理 @ControllerAdvice + @ExceptionHandle
@RestControllerAdvice
public class BusinessExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());



    /**
     * 應用到所有@RequestMapping註解方法,在其執行之前初始化資料繫結器
     * @param binder
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        System.out.println("請求有引數才進來");
    }

    /**
     * 把值繫結到Model中,使全域性@RequestMapping可以獲取到該值
     * @param model
     */
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("author", "嘟嘟MD");
    }

    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e,HttpServletRequest req){
        AjaxObject r = new AjaxObject();
        //業務異常
        if(e instanceof BusinessException){
            r.put("code", ((BusinessException) e).getCode());
            r.put("msg", ((BusinessException) e).getMsg());
        }else{//系統異常
            r.put("code","500");
            r.put("msg","未知異常,請聯絡管理員");
        }

        //使用HttpServletRequest中的header檢測請求是否為ajax, 如果是ajax則返回json, 如果為非ajax則返回view(即ModelAndView)
        String contentTypeHeader = req.getHeader("Content-Type");
        String acceptHeader = req.getHeader("Accept");
        String xRequestedWith = req.getHeader("X-Requested-With");
        if ((contentTypeHeader != null && contentTypeHeader.contains("application/json"))
                || (acceptHeader != null && acceptHeader.contains("application/json"))
                || "XMLHttpRequest".equalsIgnoreCase(xRequestedWith)) {
            return r;
        } else {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg", e.getMessage());
            modelAndView.addObject("url", req.getRequestURL());
            modelAndView.addObject("stackTrace", e.getStackTrace());
            modelAndView.setViewName("error");
            return modelAndView;
        }
    }
}

全域性異常類我用的是@RestControllerAdvice,而不是@ControllerAdvice,因為這裡返回的主要是json格式,這樣可以少寫一個@ResponseBody。

參考:

原文: http://tengj.top/2018/05/16/springboot13/  作者: 嘟嘟MD