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