異常處理最佳實踐
根據我的工作經歷來看,我主要遵循以下幾點:
- 盡量不要在代碼中寫try...catch.finally把異常吃掉。
- 異常要盡量直觀,防止被他人誤解
- 將異常分為以下幾類,業務異常,登錄狀態無效異常,(雖已登錄,且狀態有效)未授權異常,系統異常(JDK中定義Error和Exception,比如NullPointerException, ArithmeticException 和 InputMismatchException)
- 可以在某個特定的Controller中處理異常,也可以使用全局異常處理器。盡量使用全局異常處理器
使用@ControllerAdvice注釋全局異常處理器
@ControllerAdvice
public class GlobalExceptionHandler implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@ExceptionHandler
public Object businessExceptionHandler(Exception exception, HttpServletRequest req) {
DtoResult response = new DtoResult();
if (exception instanceof BusinessException) {
int code = ((BusinessException) exception).getErrorCode();
response.setCode(code > 0 ? code : DtoResult.STATUS_CODE_BUSINESS_ERROR);
response.setMessage(exception.getMessage());
} else if (exception instanceof NotAuthorizedException) {
response.setCode(DtoResult.STATUS_CODE_NOT_AUTHORIZED);
response.setMessage(exception.getMessage());
} else {
response.setCode(DtoResult.STATUS_CODE_SYSTEM_ERROR);
String profile = applicationContext.getEnvironment().getProperty("spring.profiles.active");
if (profile != GlobalConst.PROFILE_PRD) {
response.setMessage(exception.toString());
} else {
response.setMessage("系統異常");
}
logger.error("「系統異常」", exception);
}
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)) {
HttpStatus httpStatus = HttpStatus.OK;
if (response.getCode() == DtoResult.STATUS_CODE_SYSTEM_ERROR) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<>(response, httpStatus);
} else {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("detailMessage", response.getMessage());
modelAndView.addObject("url", req.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
}
}
- 使用@ControllerAdvice生命該類中的@ExceptionHandler作用於全局
- 使用@ExceptionHandler注冊異常處理器,可以注冊多個,但是不能重復,比如注冊兩個方法都用於處理Exception是不行的。
- 使用HttpServletRequest中的header檢測請求是否為ajax, 如果是ajax則返回json(即ResponseEnttiy<>), 如果為非ajax則返回view(即ModelAndView)
thymeleaf模板標簽解析錯誤
themyleaf默認使用HTML5模式,此模式比較嚴格,比如當標簽沒有正常閉合,屬性書寫不正確時都會報錯,比如以下格式
# meta標簽沒有閉合
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>程序出錯了 - 智聯</title>
</head>
<body>
<p>程序出錯了...</p>
<p>請求地址:<span th:text="${url}"></span></p>
<p>詳情:<span th:text="${detailMessage}"></span></p>
</body>
</html>
# 屬性v-cloak不符合格式
<div v-cloak></div>
解決方法
可以在配置文件中增加 spring.thymeleaf.mode=LEGACYHTML5 配置項,默認情況下是 spring.thymeleaf.mode=HTML5,
LEGACYHTML5 需要搭配第三方庫 nekohtml 才可以使用。
# 1.在 pom.xml 中增加如下內容:
<!-- https://mvnrepository.com/artifact/net.sourceforge.nekohtml/nekohtml -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
# 2.修改 application.properties 為:
############################## thymeleaf ##############################
spring.thymeleaf.cache=false
# spring.thymeleaf.mode=HTML5
spring.thymeleaf.mode=LEGACYHTML5
############################## thymeleaf ##############################
參考文檔
- SpringBoot系列學習十:統一異常處理
- @ExceptionHandler for Ajax and non-Ajax
- Spring MVC
- Spring MVC @ExceptionHandler Example
- Spring Boot中Web應用的統一異常處理
- Spring MVC View Resolver
- Exception Handling in Spring MVC
- Spring: RESTful controllers and error handling