springboot繼承AbstractErrorController實現全局的異常處理(附@ExceptionHandler的對比)

 

springboot繼承AbstractErrorController實現全局的異常處理(附@ExceptionHandler的對比)

 

 

項目中常常需要一個全局異常,防止未處理的異常信息直接暴露給用戶,影響用戶體驗。springboot中可以使用ControllerAdvice和ExceptionHandler這兩個註解來做全局異常,這種方式比較便捷,但是也有一個問題: 
ContollerAdvice只能攔截控制器中的異常,換言之,只能攔截500之類的異常,但是對於404這樣不會進入控制器處理的異常不起作用。所以我仿造springboot默認的全局處理類BasicController實現全局的異常處理,這樣就能很好的按照自己的需求處理異常了。

我們先了解一下springboot默認的異常處理是怎樣的: 
springboot會將所有的異常發送到路徑爲server.error.path(application.properties中可配置,默認爲”/error”)的控制器方法中進行處理,頁面請求和ajax請求會分別打到對應的處理方法上。具體的處理可查看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) ? modelAndView : new ModelAndView("error", model);
    }

    @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<>(body, status);
    }


瞭解完springboot的默認全局異常處理後,開始仿造着寫自定義的異常處理,BasicErrorController繼承了AbstractErrorController,所以我們也繼承AbstractErrorController。整體代碼如下:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@Slf4j
public class GlobalExceptionController extends AbstractErrorController{
    private final ErrorProperties errorProperties;
    @Autowired
    public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties=serverProperties.getError();
    }

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

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        ModelAndView modelAndView=new ModelAndView("error");
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        if(errorMap!=null) {
            /*timestamp status error message path*/
            modelAndView.addObject("msg",errorMap.get("error"));
            modelAndView.addObject("statusCode",errorMap.get("status"));
            logHandler(errorMap);
        }
        return modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ServiceResponse<Object> error(HttpServletRequest request) {
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
        logHandler(errorMap);
        return ServiceResponse.createByError("測試成功:");
    }

    private void logHandler(Map<String, Object> errorMap) {
        log.error("url:{},status{},time:{},errorMsg:{}",errorMap.get("path"),errorMap.get("status"),errorMap.get("timestamp"),errorMap.get("message"));
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request,
            MediaType produces) {
        IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
    private ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
}

 


代碼講解: 
1.開頭註解 
@Controller 
@RequestMapping(“${server.error.path:${error.path:/error}}”) 
這裏與BasicErrorControlelr一致,使用server.error.path的變量作爲映射地址。 
2.構造函數

    @Autowired
    public GlobalExceptionController(ErrorAttributes errorAttributes,ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties=serverProperties.getError();
    }

本來是抄BasicErrorController中的構造方法:

    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }


但是程序啓動的室友報錯了,原因是無法找到errorProperties的實體。於是,我便查看在自動注入BasicErrorController的類ErrorMvcAutoConfiguration,在裏面發現errorPropertie屬性原來是通過ServerProperties這個類注入的:

    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }


於是便在自定義的異常處理類構造方法中使用ServerProperties 注入errorProperties。 
3.編寫自定義的異常處理方法。 
通過查看AbstractErrorController和BasicErrorController源碼,我發現異常信息都是通過

    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
            boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }


這個方法獲取的。我還發現ErrorAttributes這個類中有一個接口可以直接獲取異常:

    /**
     * Return the underlying cause of the error or {@code null} if the error cannot be
     * extracted.
     * @param webRequest the source request
     * @return the {@link Exception} that caused the error or {@code null}
     */
    Throwable getError(WebRequest webRequest);


但是,測試之後發現通過這個getError獲取的異常都是null,一看上面的註釋,才發現這個方法只能獲取未被抽取出來的異常,而我們攔截的異常都被抽取出來了,所以獲取不到。 
所以還是仿造BasicErrorControler實現我們自己的異常處理:

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        ModelAndView modelAndView=new ModelAndView("error");
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        if(errorMap!=null) {
            /*timestamp status error message path*/
            modelAndView.addObject("msg",errorMap.get("error"));
            modelAndView.addObject("statusCode",errorMap.get("status"));
            logHandler(errorMap);
        }
        return modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ServiceResponse<Object> error(HttpServletRequest request) {
        Map<String, Object> errorMap=getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
        logHandler(errorMap);
        return ServiceResponse.createByError(errorMap.get("error"));
    }


ServiceResponse是我自己定義的ajax返回實體類.getErrorAttributes會將異常消息封裝成一個map,包含了異常的主要信息: 
timestamp:異常發生的時間 
status :http響應狀態碼 
error:異常的名稱(Exception.getName()) 
message:異常的消息(getmessage()) 
path:發生異常的路由。

這樣做其實還是一點沒有做好,那就是無法獲取到Exception異常對象。後面有時間再想想怎麼弄。 
還有一點要特別注意的地方,那就是一定要保證自己的異常處理代碼不能再發生異常,否則異常會還會被自定義的處理器接受,造成死循環。

 

 

 

springboot繼承AbstractErrorController實現全局的異常處理 - qq_29684305的博客 - CSDN博客
https://blog.csdn.net/qq_29684305/article/details/82286469

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章