Spring Cloud 如何统一异常处理?写得太好了!
Posted Java技术栈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud 如何统一异常处理?写得太好了!相关的知识,希望对你有一定的参考价值。
点击关注公众号,Java干货及时送达
作者:BNDong
链接:www.cnblogs.com/bndong/p/10135370.html
前言
在启动应用时会发现在控制台打印的日志中出现了两个路径为 [/error] 的访问地址,当系统中发送异常错误时,Spring Boot 会根据请求方式分别跳转到以 JSON 格式或以界面显示的 /error 地址中显示错误信息。
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]" ...
Spring Boot 基础就不介绍了,推荐下这个实战教程:
https://github.com/javastacks/spring-boot-best-practice
默认异常处理
使用 AJAX 方式请求时返回的 JSON 格式错误信息。
"timestamp": "2018-12-18T01:50:51.196+0000",
"status": 404,
"error": "Not Found",
"message": "No handler found for GET /err404",
"path": "/err404"
使用浏览器请求时返回的错误信息界面。
自定义异常处理
引入依赖
<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 是一个模板引擎,用于我们设置错误输出模板。最新 Spring Boot 面试题整理好了,大家可以在Java面试库小程序在线刷题。
增加配置
# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为工程中的资源文件建立映射
spring.resources.add-mappings=false
spring:
# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
mvc:
throw-exception-if-no-handler-found: true
# 不要为工程中的资源文件建立映射
resources:
add-mappings: false
Spring Boot 基础就不介绍了,推荐下这个实战教程:
https://github.com/javastacks/spring-boot-best-practice
新建错误信息实体
/**
* 信息实体
*/
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;
/**
* 业务异常
*/
public class BusinessException extends BasicException
private static final long serialVersionUID = 1L;
public BusinessException(int code, String message)
super(code, message);
BasicException 继承了 RuntimeException ,并在原有的 Message 基础上增加了错误码 code 的内容。而 BusinessException 则是在业务中具体使用的自定义异常类,起到了对不同的异常信息进行分类的作用。分享资料:Spring Boot 学习笔记。
新建 error.ftl 模板文件
位置:/src/main/resources/templates/ 用于显示错误信息
<!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());
/**
* 业务异常处理
*/
@ExceptionHandler(value = BasicException.class)
private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response)
return commonHandler(request, response,
exception.getClass().getSimpleName(),
exception.getCode(),
exception.getMessage());
/**
* 表单验证异常处理
*/
@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;
/**
* 异常处理数据处理
*/
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;
点击关注公众号,Java干货及时送达
该异常来自于表单验证框架 Hibernate validation,当字段验证未通过时会抛出此异常。
编写测试 Controller
@RestController
public class TestController
@RequestMapping(value = "err")
public void error()
throw new BusinessException(400, "业务异常错误信息");
@RequestMapping(value = "err2")
public void error2()
throw new NullPointerException("手动抛出异常信息");
@RequestMapping(value = "err3")
public int error3()
int a = 10 / 0;
return a;
使用 AJAX 方式请求时返回的 JSON 格式错误信息。最新 Spring Boot 面试题整理好了,大家可以在Java面试库小程序在线刷题。
# /err
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data":
"path": "/err",
"code": 400,
"error": "BusinessException",
"message": "业务异常错误信息",
"timestamp": "2018-12-18 11:09:00"
# /err2
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data":
"path": "/err2",
"code": 500,
"error": "NullPointerException",
"message": "手动抛出异常信息",
"timestamp": "2018-12-18 11:15:15"
# /err3
"msg": "应用程序异常",
"code": -1,
"status_code": 0,
"data":
"path": "/err3",
"code": 500,
"error": "ArithmeticException",
"message": "/ by zero",
"timestamp": "2018-12-18 11:15:46"
# /err404
"msg": "应用程序异常",
"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"
使用浏览器请求时返回的错误信息界面。
示例代码:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul
参考资料:
《微服务 分布式架构开发实战》 龚鹏 著
https://www.jianshu.com/p/1a49fa436623
关注Java技术栈看更多干货
获取 Spring Boot 实战笔记!
以上是关于Spring Cloud 如何统一异常处理?写得太好了!的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud 微服务优雅下线 + 灰度发布的正确姿势,写得太好了!
Spring Boot + Web Socket 打造实时监控异常,写得太好了!
Spring Boot + Web Socket 打造实时监控异常,写得太好了!