?SpringCloud:統(tǒng)一異常處理
今日推薦
作者:BNDong
www.cnblogs.com/bndong/p/10135370.html
在啟動(dòng)應(yīng)用時(shí)會(huì)發(fā)現(xiàn)在控制臺(tái)打印的日志中出現(xiàn)了兩個(gè)路徑為 {[/error]} 的訪問地址,當(dāng)系統(tǒng)中發(fā)送異常錯(cuò)誤時(shí),Spring Boot 會(huì)根據(jù)請(qǐng)求方式分別跳轉(zhuǎn)到以 JSON 格式或以界面顯示的 /error 地址中顯示錯(cuò)誤信息。
2018-12-18 09:36:24.627 INFO 19040 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" ...
2018-12-18 09:36:24.632 INFO 19040 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" ...
默認(rèn)異常處理
使用 AJAX 方式請(qǐng)求時(shí)返回的 JSON 格式錯(cuò)誤信息。
{
"timestamp": "2018-12-18T01:50:51.196+0000",
"status": 404,
"error": "Not Found",
"message": "No handler found for GET /err404",
"path": "/err404"
}
使用瀏覽器請(qǐng)求時(shí)返回的錯(cuò)誤信息界面。

自定義異常處理
引入依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
fastjson 是 JSON 序列化依賴, spring-boot-starter-freemarker 是一個(gè)模板引擎,用于我們?cè)O(shè)置錯(cuò)誤輸出模板。
增加配置
properties
# 出現(xiàn)錯(cuò)誤時(shí), 直接拋出異常(便于異常統(tǒng)一處理,否則捕獲不到404)
spring.mvc.throw-exception-if-no-handler-found=true
# 不要為工程中的資源文件建立映射
spring.resources.add-mappings=false
yml
spring:
# 出現(xiàn)錯(cuò)誤時(shí), 直接拋出異常(便于異常統(tǒng)一處理,否則捕獲不到404)
mvc:
throw-exception-if-no-handler-found: true
# 不要為工程中的資源文件建立映射
resources:
add-mappings: false
新建錯(cuò)誤信息實(shí)體
/**
* 信息實(shí)體
*/
public class ExceptionEntity implements Serializable {
private static final long serialVersionUID = 1L;
private String message;
private int code;
private String error;
private String path;
@JSONField(format = "yyyy-MM-dd hh:mm:ss")
private Date timestamp = new Date();
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
}
新建自定義異常
/**
* 自定義異常
*/
public class BasicException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code = 0;
public BasicException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return this.code;
}
}
/**
* 業(yè)務(wù)異常
*/
public class BusinessException extends BasicException {
private static final long serialVersionUID = 1L;
public BusinessException(int code, String message) {
super(code, message);
}
}
BasicException 繼承了 RuntimeException ,并在原有的 Message 基礎(chǔ)上增加了錯(cuò)誤碼 code 的內(nèi)容。而 BusinessException 則是在業(yè)務(wù)中具體使用的自定義異常類,起到了對(duì)不同的異常信息進(jìn)行分類的作用。
新建 error.ftl 模板文件
位置:/src/main/resources/templates/ 用于顯示錯(cuò)誤信息
<!DOCTYPE html>
<html>
<head>
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
h2{
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 18px;
border-bottom: 1px solid #eee;
}
/* Exception Variables */
.exception-var table{
width: 100%;
max-width: 500px;
margin: 12px 0;
box-sizing: border-box;
table-layout:fixed;
word-wrap:break-word;
}
.exception-var table caption{
text-align: left;
font-size: 16px;
font-weight: bold;
padding: 6px 0;
}
.exception-var table caption small{
font-weight: 300;
display: inline-block;
margin-left: 10px;
color: #ccc;
}
.exception-var table tbody{
font-size: 13px;
font-family: Consolas,"Liberation Mono",Courier,"微軟雅黑";
}
.exception-var table td{
padding: 0 6px;
vertical-align: top;
word-break: break-all;
}
.exception-var table td:first-child{
width: 28%;
font-weight: bold;
white-space: nowrap;
}
.exception-var table td pre{
margin: 0;
}
</style>
</head>
<body>
<div class="exception-var">
<h2>Exception Datas</h2>
<table>
<tbody>
<tr>
<td>Code</td>
<td>
${(exception.code)!}
</td>
</tr>
<tr>
<td>Time</td>
<td>
${(exception.timestamp?datetime)!}
</td>
</tr>
<tr>
<td>Path</td>
<td>
${(exception.path)!}
</td>
</tr>
<tr>
<td>Exception</td>
<td>
${(exception.error)!}
</td>
</tr>
<tr>
<td>Message</td>
<td>
${(exception.message)!}
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
編寫全局異常控制類
/**
* 全局異常控制類
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 404異常處理
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.NOT_FOUND.value(),
exception.getMessage());
}
/**
* 405異常處理
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ModelAndView errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.METHOD_NOT_ALLOWED.value(),
exception.getMessage());
}
/**
* 415異常處理
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ModelAndView errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),
exception.getMessage());
}
/**
* 500異常處理
*/
@ExceptionHandler(value = Exception.class)
public ModelAndView errorHandler (HttpServletRequest request, Exception exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
exception.getMessage());
}
/**
* 業(yè)務(wù)異常處理
*/
@ExceptionHandler(value = BasicException.class)
private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response) {
return commonHandler(request, response,
exception.getClass().getSimpleName(),
exception.getCode(),
exception.getMessage());
}
/**
* 表單驗(yàn)證異常處理
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public ExceptionEntity validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
Map<String,String> errors = new HashMap<>();
for (FieldError error:fieldErrors) {
errors.put(error.getField(), error.getDefaultMessage());
}
ExceptionEntity entity = new ExceptionEntity();
entity.setMessage(JSON.toJSONString(errors));
entity.setPath(request.getRequestURI());
entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
entity.setError(exception.getClass().getSimpleName());
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return entity;
}
/**
* 異常處理數(shù)據(jù)處理
*/
private ModelAndView commonHandler (HttpServletRequest request, HttpServletResponse response,
String error, int httpCode, String message) {
ExceptionEntity entity = new ExceptionEntity();
entity.setPath(request.getRequestURI());
entity.setError(error);
entity.setCode(httpCode);
entity.setMessage(message);
return determineOutput(request, response, entity);
}
/**
* 異常輸出處理
*/
private ModelAndView determineOutput(HttpServletRequest request, HttpServletResponse response, ExceptionEntity entity) {
if (!(
request.getHeader("accept").contains("application/json")
|| (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest"))
)) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception", entity);
return modelAndView;
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setCharacterEncoding("UTF8");
response.setHeader("Content-Type", "application/json");
try {
response.getWriter().write(ResultJsonTools.build(
ResponseCodeConstant.SYSTEM_ERROR,
ResponseMessageConstant.APP_EXCEPTION,
JSONObject.parseObject(JSON.toJSONString(entity))
));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
@ControllerAdvice
作用于類上,用于標(biāo)識(shí)該類用于處理全局異常。
@ExceptionHandler
作用于方法上,用于對(duì)攔截的異常類型進(jìn)行處理。value 屬性用于指定具體的攔截異常類型,如果有多個(gè) ExceptionHandler 存在,則需要指定不同的 value 類型,由于異常類擁有繼承關(guān)系,所以 ExceptionHandler 會(huì)首先執(zhí)行在繼承樹中靠前的異常類型。
BindException
該異常來自于表單驗(yàn)證框架 Hibernate validation,當(dāng)字段驗(yàn)證未通過時(shí)會(huì)拋出此異常。
編寫測試 Controller
@RestController
public class TestController {
@RequestMapping(value = "err")
public void error(){
throw new BusinessException(400, "業(yè)務(wù)異常錯(cuò)誤信息");
}
@RequestMapping(value = "err2")
public void error2(){
throw new NullPointerException("手動(dòng)拋出異常信息");
}
@RequestMapping(value = "err3")
public int error3(){
int a = 10 / 0;
return a;
}
}
使用 AJAX 方式請(qǐng)求時(shí)返回的 JSON 格式錯(cuò)誤信息。
# /err
{
"msg": "應(yīng)用程序異常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err",
"code": 400,
"error": "BusinessException",
"message": "業(yè)務(wù)異常錯(cuò)誤信息",
"timestamp": "2018-12-18 11:09:00"
}
}
# /err2
{
"msg": "應(yīng)用程序異常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err2",
"code": 500,
"error": "NullPointerException",
"message": "手動(dòng)拋出異常信息",
"timestamp": "2018-12-18 11:15:15"
}
}
# /err3
{
"msg": "應(yīng)用程序異常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err3",
"code": 500,
"error": "ArithmeticException",
"message": "/ by zero",
"timestamp": "2018-12-18 11:15:46"
}
}
# /err404
{
"msg": "應(yīng)用程序異常",
"code": -1,
"status_code": 0,
"data": {
"path": "/err404",
"code": 404,
"error": "NoHandlerFoundException",
"message": "No handler found for GET /err404",
"timestamp": "2018-12-18 11:16:11"
}
}
使用瀏覽器請(qǐng)求時(shí)返回的錯(cuò)誤信息界面。




示例代碼:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul
參考資料
《微服務(wù) 分布式架構(gòu)開發(fā)實(shí)戰(zhàn)》 龔鵬 著
https://www.jianshu.com/p/1a49fa436623
推薦文章
更多項(xiàng)目源碼
