Spring Boot异常处理
一.默认映射
我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射:/error
,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。
选择一个之前实现过的Web应用为基础,启动该应用,访问一个不存在的URL,或是修改处理内容,直接抛出异常,如:
@RequestMapping("/hello")
public String hello() throws Exception {
throw new Exception("发生错误");
}
此时,可以看到类似下面的报错页面,该页面就是Spring Boot提供的默认error映射页面。
alt=默认的错误页面
二.映射到页面
虽然,Spring Boot中实现了默认的error映射,但是在实际应用中,上面你的错误页面对用户来说并不够友好,我们通常需要去实现我们自己的异常提示。
下面我们以之前的Web应用例子为基础,进行统一异常处理的改造。
- 创建全局异常处理类:通过使用
@ControllerAdvice
定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler
用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html
中
@ControllerAdvice
class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
} }
- 实现
error.html
页面展示:在templates
目录下创建error.html
,将请求的URL和Exception对象的message输出。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>统一异常处理</title>
</head>
<body>
<h1>Error Handler</h1>
<div th:text="${url}"></div>
<div th:text="${exception.message}"></div>
</body>
</html>
启动该应用,访问:http://localhost:8080/hello
,可以看到如下错误提示页面。
alt=自定义的错误页面
通过实现上述内容之后,我们只需要在Controller
中抛出Exception
,当然我们可能会有多种不同的Exception
。然后在@ControllerAdvice
类中,根据抛出的具体Exception
类型匹配@ExceptionHandler
中配置的异常类型来匹配错误映射和处理。
三.返回JSON格式
在上述例子中,通过@ControllerAdvice
统一定义不同Exception映射到不同错误处理页面。而当我们要实现RESTful API时,返回的错误是JSON格式的数据,而不是HTML页面,这时候我们也能轻松支持。
在上述例子中,通过@ControllerAdvice
统一定义不同Exception映射到不同错误处理页面。而当我们要实现RESTful API时,返回的错误是JSON格式的数据,而不是HTML页面,这时候我们也能轻松支持。
本质上,只需在@ExceptionHandler
之后加入@ResponseBody
,就能让处理函数return的内容转换为JSON格式。
下面以一个具体示例来实现返回JSON格式的异常处理。
- 创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据
public class ErrorInfo<T> { public static final Integer OK = 0;
public static final Integer ERROR = 100; private Integer code;
private String message;
private String url;
private T data; // 省略getter和setter }
- 创建一个自定义异常,用来实验捕获该异常,并返回json
public class MyException extends Exception { public MyException(String message) {
super(message);
} }
Controller
中增加json映射,抛出MyException
异常
@Controller
public class HelloController { @RequestMapping("/json")
public String json() throws MyException {
throw new MyException("发生错误2");
} }
- 为
MyException
异常创建对应的处理
@ControllerAdvice
public class GlobalExceptionHandler { @ExceptionHandler(value = MyException.class)
@ResponseBody
public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
ErrorInfo<String> r = new ErrorInfo<>();
r.setMessage(e.getMessage());
r.setCode(ErrorInfo.ERROR);
r.setData("Some Data");
r.setUrl(req.getRequestURL().toString());
return r;
} }
- 启动应用,访问:http://localhost:8080/json,可以得到如下返回内容:
{
code: 100,
data: "Some Data",
message: "发生错误2",
url: "http://localhost:8080/json"
}
代码示例至此,已完成在Spring Boot中创建统一的异常处理,实际实现还是依靠Spring MVC的注解,更多更深入的使用可参考Spring MVC的文档。
本文的相关例子可以查看下面仓库中的chapter3-1-6
目录:
- Github:https://github.com/dyc87112/SpringBoot-Learning
- Gitee:https://gitee.com/didispace/SpringBoot-Learning
附:
当一个Controller中有方法加了@ExceptionHandler之后,这个Controller其他方法中没有捕获的异常就会以参数的形式传入加了@ExceptionHandler注解的那个方法中。
首先需要为自己的系统设计一个自定义的异常类,通过它来传递状态码。
/** * Created by liuruijie.
* 自定义异常
*/
public class SystemException extends RuntimeException{
private String code;//状态码
public SystemException(String message, String code) {
super(message);
this.code = code;
}
public String getCode() {
return code;
}
}
第一种思路,设计一个基类。
/**
* Created by liuruijie.
* 处理异常的类,需要处理异常的Controller直接继承这个类
*/
public class BaseController {
/**
* 处理Controller抛出的异常
* @param e 异常实例
* @return Controller层的返回值
*/
@ExceptionHandler
@ResponseBody
public Object expHandler(Exception e){
if(e instanceof SystemException){
SystemException ex= (SystemException) e;
return WebResult.buildResult().status(ex.getCode())
.msg(ex.getMessage());
}else{
e.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系统错误");
}
}
}
之后所有需要异常处理的Controller都继承这个类,从而获取到异常处理的方法。
虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。
第二种思路,将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现)
/**
* Created by liuruijie.
* 接口形式的异常处理
*/
public interface DataExceptionSolver {
@ExceptionHandler
@ResponseBody
default Object exceptionHandler(Exception e){
try {
throw e;
} catch (SystemException systemException) {
systemException.printStackTrace();
return WebResult.buildResult().status(systemException.getCode())
.msg(systemException.getMessage());
} catch (Exception e1){
e1.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系统错误");
}
}
}
第三种思路,使用加强Controller做全局异常处理。
所谓加强Controller就是@ControllerAdvice注解,有这个注解的类中的方法的某些注解会应用到所有的Controller里,其中就包括@ExceptionHandler注解。
于是可以写一个全局的异常处理类:
/**
* Created by liuruijie on 2016/12/28.
* 全局异常处理,捕获所有Controller中抛出的异常。
*/
@ControllerAdvice
public class GlobalExceptionHandler {
//处理自定义的异常
@ExceptionHandler(SystemException.class)
@ResponseBody
public Object customHandler(SystemException e){
e.printStackTrace();
return WebResult.buildResult().status(e.getCode()).msg(e.getMessage());
}
//其他未处理的异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHandler(Exception e){
e.printStackTrace();
return WebResult.buildResult().status(Config.FAIL).msg("系统错误");
}
}
这个类中只处理了两个异常,但是已经满足了大部分需要,如果还有需要特殊处理的地方,可以再加上处理的方法就行了。第三种实现方式是目前我知道的最优雅的方式了。
如此,我们现在的Controller中的方法就可以很简洁了,比如处理登陆的逻辑就可以这样简单的写:
/**
* Created by liuruijie on 2016/12/28.
* 账号
*/
@RestController
@RequestMapping("passport")
public class PassportController {
PassportService passportService;
@RequestMapping("login")
public Object doLogin(HttpSession session, String username, String password){
User user = passportService.doLogin(username, password);
session.setAttribute("user", user);
return WebResult.buildResult().redirectUrl("/student/index");
}
}
而在passprotService的doLogin方法中,可能会抛出用户名或密码错误等异常,然后就会交由GlobalExceptionHandler去处理,直接返回异常信息给前端,然后前端也不需要关心是否返回了异常,因为这些都已经定义好了。
前端js代码只需要这样写:
//登陆
AJAX.POST("/passport/login", {
username:name,
password:psw
})
一个异常在其中流转的过程为:
比如doLogin方法抛出了自定义异常,其code为:FAIL,message为:用户名或密码错误,由于在controller的方法中没有捕获这个异常,所以会将异常抛给GlobalExceptionHandler,然后GlobalExceptionHandler通过WebResult将状态码和提示信息返回给前端,前端通过默认的处理函数,弹框提示用户“用户名或密码错误”。而对于这样的一次交互,我们根本不用编写异常处理部分的逻辑。
到这里,代码已经简洁了很多,而且重用性大大提高。
HTTP状态码
100 到199 的状态码代表信息,描述对于请求的处理。
200 到 299 的状态码表示客户端发来的请求已经被接收并正确处理。
300 到 399 的状态码表示客户端需要进一步的处理才能完成请求,比如重定向到另一个地址。
400 到 499 的状态码表示客户端的请求有错误,需要修正。404就是这种情况。
500 到 599 的状态码表示服务器在处理客户端请求时发生了内部错误。
Spring Boot异常处理的更多相关文章
- Spring Boot 异常处理
Spring Boot 异常处理 本节介绍一下 Spring Boot 启动时是如何处理异常的?核心类是 SpringBootExceptionReporter 和 SpringBootExcepti ...
- Spring Boot异常处理详解
在Spring MVC异常处理详解中,介绍了Spring MVC的异常处理体系,本文将讲解在此基础上Spring Boot为我们做了哪些工作.下图列出了Spring Boot中跟MVC异常处理相关的类 ...
- Spring boot 异常处理配置
1. 新建Maven项目 exception 2. pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0&quo ...
- Spring Boot 异常处理静止trace
概述 在spring boot 2.2 中 默认状态为status 999 private void addStatus(Map<String, Object> errorAttribut ...
- spring boot 异常处理(转)
spring boot在异常的处理中,默认实现了一个EmbeddedServletContainerCustomizer并定义了一个错误页面到"/error"中,在ErrorMvc ...
- Spring Boot 知识图谱
最近有意重新学习下SpringBoot知识,特地总结了SpringBoot的知识点,对于想要学习的人来说,够用. SpringBoot学习路径 第一部分:了解 Spring Boot Spring B ...
- 40 篇原创干货,带你进入 Spring Boot 殿堂!
两个月前,松哥总结过一次已经完成的 Spring Boot 教程,当时感受到了小伙伴们巨大的热情. 两个月过去了,松哥的 Spring Boot 教程又更新了不少,为了方便小伙伴们查找,这里再给大家做 ...
- Spring Boot 日志处理你还在用Logback?
▶ Log4j2 性能 https://logging.apache.org/log4j/2.x/performance.html ▶ Spring Boot 依赖与配置 Maven 依赖 <! ...
- 天天玩微信,Spring Boot 开发私有即时通信系统了解一下
1/ 概述 利用Spring Boot作为基础框架,Spring Security作为安全框架,WebSocket作为通信框架,实现点对点聊天和群聊天. 2/ 所需依赖 Spring Boot 版本 ...
随机推荐
- ECMAscript5 新增数组内函数
indexOf() 格式:数组.indexOf(item, start) 功能:从start这个下标开始,查找item在数组中的第一次出现的下标. 参数:item 我们要去查找的元素 start从哪个 ...
- jmeter打开其他设备转过来的历史脚本出现报错
报错大概如下 missing class com.thoughtworks.xstream.converters.ConversionException Debugging information 还 ...
- python 全栈开发,Day106(结算中心(详细),立即支付)
昨日内容回顾 1. 为什么要开发路飞学城? 提供在线教育的学成率: 特色: 学,看视频,单独录制增加趣味性. 练,练习题 改,改学生代码 管,管理 测,阶段考核 线下:8次留级考试 2. 组织架构 - ...
- JQuery+Ajax实战三级下拉列表联动(八)
本片文章为练习,项目中不会这样写: 一:涉及到的知识点: jQuery Dom操作 jQuery Ajax操作 ASP.net中的json操作 二:用了自动代码生成器 1.Dal层的代码: publi ...
- 【C++ Primer 第13章】3. 交换操作
交换操作 class HasPtr { friend void swap(HasPtr &rhs, HasPtr &yhs); //其他成员定义 }; void swap(HasPtr ...
- CCF CSP认证考试试题
1. 201803-1 跳一跳 试题编号: 201803-1 试题名称: 跳一跳 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 近来,跳一跳这款小游戏风靡全国,受到不少玩家的 ...
- Redis设置Key/value的规则定义和注意事项(附工具类)
对于redis的存储key/value键值对,经过多次踩坑之后,我们总结了一套规则:这篇文章主要讲解定义key/value键值对时的定义规则和注意事项. 前面一篇文章讲了如何定义Redis的客户端和D ...
- kafka中生产者和消费者API
使用idea实现相关API操作,先要再pom.xml重添加Kafka依赖: <dependency> <groupId>org.apache.kafka</groupId ...
- jquery数组(sort() 排序)
HTML: <h3>字符串数组排序前</h3> <div id="show5"></div> <h3>排序后</h ...
- 网络流24题 第五题 - PowerOJ1740 CodeVS1905 圆桌问题 二分图多重匹配 网络最大流
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - PowerOJ1740 - 有SPJ - 推荐 题目传送门 - CodeVS1905 - 无SPJ - 0% ...