Spring Cloud 如何统一异常处理?写得太好了!

Spring Cloud 如何统一异常处理?写得太好了!

点击关注公众号,Java干货及时送达

作者:BNDong

链接:www.cnblogs.com/bndong/p/10135370.html

前言在启动应用时会发现在控制台打印的日志中出现了两个路径为 {[/error]} 的访问地址,当系统中发送异常错误时,Spring Boot 会根据请求方式分别跳转到以 JSON 格式或以界面显示的 /error 地址中显示错误信息。

代码语言:javascript代码运行次数:0运行复制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 格式错误信息。

代码语言:javascript代码运行次数:0运行复制{

"timestamp": "2018-12-18T01:50:51.196+0000",

"status": 404,

"error": "Not Found",

"message": "No handler found for GET /err404",

"path": "/err404"

}使用浏览器请求时返回的错误信息界面。

自定义异常处理引入依赖代码语言:javascript代码运行次数:0运行复制

com.alibaba

fastjson

1.2.54

org.springframework.boot

spring-boot-starter-freemarker

fastjson 是 JSON 序列化依赖, spring-boot-starter-freemarker 是一个模板引擎,用于我们设置错误输出模板。最新 Spring Boot 面试题整理好了,大家可以在Java面试库小程序在线刷题。

增加配置

代码语言:javascript代码运行次数:0运行复制# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)

spring.mvc.throw-exception-if-no-handler-found=true

# 不要为工程中的资源文件建立映射

spring.resources.add-mappings=false代码语言:javascript代码运行次数:0运行复制spring:

# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)

mvc:

throw-exception-if-no-handler-found: true

# 不要为工程中的资源文件建立映射

resources:

add-mappings: falseSpring Boot 基础就不介绍了,推荐下这个实战教程:

https://github.com/javastacks/spring-boot-best-practice

新建错误信息实体代码语言:javascript代码运行次数:0运行复制/**

* 信息实体

*/

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;

}

}新建自定义异常代码语言:javascript代码运行次数:0运行复制/**

* 自定义异常

*/

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;

}

}代码语言:javascript代码运行次数:0运行复制/**

* 业务异常

*/

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/ 用于显示错误信息

代码语言:javascript代码运行次数:0运行复制

Exception Datas

Code

${(exception.code)!}

Time

${(exception.timestamp?datetime)!}

Path

${(exception.path)!}

Exception

${(exception.error)!}

Message

${(exception.message)!}

编写全局异常控制类代码语言:javascript代码运行次数:0运行复制/**

* 全局异常控制类

*/

@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 fieldErrors = exception.getBindingResult().getFieldErrors();

Map 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;

}

}

}@ControllerAdvice

作用于类上,用于标识该类用于处理全局异常。

点击关注公众号,Java干货及时送达

@ExceptionHandler

作用于方法上,用于对拦截的异常类型进行处理。value 属性用于指定具体的拦截异常类型,如果有多个 ExceptionHandler 存在,则需要指定不同的 value 类型,由于异常类拥有继承关系,所以 ExceptionHandler 会首先执行在继承树中靠前的异常类型。

BindException

该异常来自于表单验证框架 Hibernate validation,当字段验证未通过时会抛出此异常。

编写测试 Controller代码语言:javascript代码运行次数:0运行复制@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面试库小程序在线刷题。

代码语言:javascript代码运行次数:0运行复制# /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/1a49fa436623Spring Boot 定时任务开启后,怎么自动停止?

工作 3 年的同事不知道如何回滚代码!

23 种设计模式实战(很全)

Spring Boot 保护敏感配置的 4 种方法!

面了个 5 年 Java,两个线程数据交换都不会!

阿里为什么推荐使用 LongAdder?

新来一个技术总监:禁止戴耳机写代码。。

重磅!Spring Boot 2.7 正式发布

Java 18 正式发布,finalize 被弃用。。

Spring Boot Admin 横空出世!

Spring Boot 学习笔记,这个太全了!

关注Java技术栈看更多干货

获取 Spring Boot 实战笔记!

相关推荐

释放磁盘空间的 6 种最有效方法 Windows 11
365bet网投官网

释放磁盘空间的 6 种最有效方法 Windows 11

📅 07-19 👁️ 8837
乳和霜哪个可以不用 霜和乳有什么区别
365买球平台下载苹果

乳和霜哪个可以不用 霜和乳有什么区别

📅 07-16 👁️ 187
手机网络随意切换,Android用户必看的小技巧与潜在问题解析!
每天坚持“踮脚”的人,身体会发生什么变化?8个好处不请自来!
作者乐胥相关作品集
365买球平台下载苹果

作者乐胥相关作品集

📅 07-07 👁️ 8063
阴阳师提灯小僧最多刷新点介绍
365bet网投官网

阴阳师提灯小僧最多刷新点介绍

📅 07-15 👁️ 7357