前言
本篇文章主要介绍的是SpringBoot项目进行全局异常的处理。
SpringBoot全局异常准备
说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码。
开发准备
环境要求JDK:1.8SpringBoot:1.5.17.RELEASE
首先还是Maven的相关依赖:
<code><
properties
><
project.build.sourceEncoding
>UTF-8project.build.sourceEncoding
><
java.version
>1.8java.version
><
maven.compiler.source
> 1.8maven.compiler.source
><
maven.compiler.target
>1.8maven.compiler.target
>properties
><
parent
><
groupId
>org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-parentartifactId
><
version
>1.5.17.RELEASEversion
><
relativePath
/>parent
><
dependencies
><
dependency
><
groupId
>org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-webartifactId
>dependency
><
dependency
><
groupId
> org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-testartifactId
><
scope
>testscope
>dependency
><
dependency
><
groupId
>com.alibabagroupId
><
artifactId
>fastjsonartifactId
><
version
>1.2.41version
>dependency
>dependencies
> /<code>
配置文件这块基本不需要更改,全局异常的处理只需在代码中实现即可。
代码编写
SpringBoot的项目已经对有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理。SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
我们根据下面的这个示例来看该注解是如何使用吧。
示例代码:
<code>public
class
MyExceptionHandler
{public
String exceptionHandler(Exception e){ System.out
.println("未知异常!原因是:"
+e);return
e.getMessage(); } } /<code>
上述的示例中,我们对捕获的异常进行简单的二次处理,返回异常的信息,虽然这种能够让我们知道异常的原因,但是在很多的情况下来说,可能还是不够人性化,不符合我们的要求。那么我们这里可以通过自定义的异常类以及枚举类来实现我们想要的那种数据吧。
自定义基础接口类
首先定义一个基础的接口类,自定义的错误描述枚举类需实现该接口。代码如下:
<code>public
interface
BaseErrorInfoInterface
{String
getResultCode
();String
getResultMsg
(); } /<code>
自定义枚举类
然后我们这里在自定义一个枚举类,并实现该接口。代码如下:
<code>public
enum
CommonEnumimplements
BaseErrorInfoInterface { SUCCESS("200"
,"成功!"
), BODY_NOT_MATCH("400"
,"请求的数据格式不符!"
), SIGNATURE_NOT_MATCH("401"
,"请求的数字签名不匹配!"
), NOT_FOUND("404"
,"未找到该资源!"
), INTERNAL_SERVER_ERROR("500"
,"服务器内部错误!"
), SERVER_BUSY("503"
,"服务器正忙,请稍后再试!"
) ;private
String
resultCode;private
String
resultMsg; CommonEnum(String
resultCode,String
resultMsg) {this
.resultCode = resultCode;this
.resultMsg = resultMsg; }public
String
getResultCode() {return
resultCode; }public
String
getResultMsg() {return
resultMsg; } } /<code>
自定义异常类
然后我们在来自定义一个异常类,用于处理我们发生的业务异常。代码如下:
<code>public
class
BizException
extends
RuntimeException
{private
static
final
long
serialVersionUID =1L
;protected
String errorCode;protected
String errorMsg;public
BizException
()
{super
(); }public
BizException
(BaseErrorInfoInterface errorInfoInterface)
{super
(errorInfoInterface.getResultCode());this
.errorCode = errorInfoInterface.getResultCode();this
.errorMsg = errorInfoInterface.getResultMsg(); }
public
BizException
(BaseErrorInfoInterface errorInfoInterface, Throwable cause)
{super
(errorInfoInterface.getResultCode(), cause);this
.errorCode = errorInfoInterface.getResultCode();this
.errorMsg = errorInfoInterface.getResultMsg(); }public
BizException
(String errorMsg)
{super
(errorMsg);this
.errorMsg = errorMsg; }public
BizException
(String errorCode, String errorMsg)
{super
(errorCode);this
.errorCode = errorCode;this
.errorMsg = errorMsg; }public
BizException
(String errorCode, String errorMsg, Throwable cause)
{super
(errorCode, cause);this
.errorCode = errorCode;this
.errorMsg = errorMsg; }public
StringgetErrorCode
()
{return
errorCode; }public
void
setErrorCode
(String errorCode)
{this
.errorCode = errorCode; }public
StringgetErrorMsg
()
{return
errorMsg; }public
void
setErrorMsg
(String errorMsg)
{this
.errorMsg = errorMsg; }public
StringgetMessage
()
{return
errorMsg; }public
ThrowablefillInStackTrace
()
{return
this
; } } /<code>
自定义数据格式
顺便这里我们定义一下数据的传输格式。代码如下:
<code>public
class
ResultBody
{private
String code;private
String message;private
Object result;public
ResultBody
() { }public
ResultBody
(BaseErrorInfoInterface errorInfo
) {this
.code = errorInfo.getResultCode();this
.message = errorInfo.getResultMsg(); }public
StringgetCode
() {return
code; }public
void
setCode
(String code
) {this
.code = code; }public
StringgetMessage
() {return
message; }public
void
setMessage
(String message
) {this
.message = message; }public
ObjectgetResult
() {return
result; }public
void
setResult
(Object result
) {this
.result = result; }public
static
ResultBodysuccess
() {return
success(null
); }public
static
ResultBodysuccess
(Object data
) { ResultBody rb =new
ResultBody(); rb.setCode(CommonEnum.SUCCESS.getResultCode()); rb.setMessage(CommonEnum.SUCCESS.getResultMsg()); rb.setResult(data);return
rb; }public
static
ResultBodyerror
(BaseErrorInfoInterface errorInfo
) { ResultBody rb =new
ResultBody(); rb.setCode(errorInfo.getResultCode()); rb.setMessage(errorInfo.getResultMsg()); rb.setResult(null
);return
rb; }public
static
ResultBodyerror
(String code, String message
) { ResultBody rb =new
ResultBody(); rb.setCode(code); rb.setMessage(message); rb.setResult(null
);return
rb; }public
static
ResultBodyerror
(String message
) { ResultBody rb =new
ResultBody(); rb.setCode("-1"
); rb.setMessage(message); rb.setResult(null
);return
rb; } @Override
public
StringtoString
() {return
JSONObject.toJSONString(this
); } } /<code>
自定义全局异常处理类
最后我们在来编写一个自定义全局异常处理的类。代码如下:
<code>public
class
GlobalExceptionHandler
{private
staticfinal
Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.
class
);public
ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){ logger.error("发生业务异常!原因是:{}"
,e.getErrorMsg());return
ResultBody.error(e.getErrorCode(),e.getErrorMsg()); }public
ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){ logger.error("发生空指针异常!原因是:"
,e);return
ResultBody.error(CommonEnum.BODY_NOT_MATCH); }public
ResultBody exceptionHandler(HttpServletRequest req, Exception e){ logger.error("未知异常!原因是:"
,e);return
ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR); } } /<code>
因为这里我们只是用于做全局异常处理的功能实现以及测试,所以这里我们只需在添加一个实体类和一个控制层类即可。
实体类
又是万能的用户表 (▽)
代码如下:
<code>public
class
User
implements
Serializable
{private
static
final
long
serialVersionUID =1L
;private
int
id;private
String name;private
int
age;public
User
()
{ }public
int
getId
()
{return
id; }public
void
setId
(
int
id) {this
.id = id; }public
StringgetName
()
{return
name; }public
void
setName
(String name)
{this
.name = name; }public
int
getAge
()
{return
age; }public
void
setAge
(
int
age) {this
.age = age; }public
StringtoString
()
{return
JSONObject.toJSONString(this
); } } /<code>
Controller 控制层
控制层这边也比较简单,使用Restful风格实现的CRUD功能,不同的是这里我故意弄出了一些异常,好让这些异常被捕获到然后处理。这些异常中,有自定义的异常抛出,也有空指针的异常抛出,当然也有不可预知的异常抛出(这里我用类型转换异常代替),那么我们在完成代码编写之后,看看这些异常是否能够被捕获处理成功吧!
代码如下:
<code>public
class
UserRestController
{public
boolean insert( User user) { System.out
.println("开始新增..."
);if
(user.getName()==null
){throw
new BizException("-1"
,"用户姓名不能为空!"
); }return
true
; }public
boolean update( User user) { System.out
.println("开始更新..."
); String str=null
; str.equals("111"
);return
true
; }public
boolean delete( User user) { System.out
.println("开始删除..."
); Integer.parseInt("abc123"
);return
true
; }public
List findByUser(User user) { System.out
.println("开始查询..."
); List userList =new ArrayList<>(); User user2=new User(); user2.setId(1L
); user2.setName("xuwujing"
); user2.setAge(18
); userList.add(user2);return
userList; } } /<code>
App 入口
和普通的SpringBoot项目基本一样。
代码如下:
<code>public
class
App
{public
static
void
main
( String[] args )
{ SpringApplication.run(App.
class
,args
); System.out.println("程序正在运行..."
); } } /<code>
功能测试
我们成功启动该程序之后,使用Postman工具来进行接口测试。
首先进行查询,查看程序正常运行是否ok,使用GET 方式进行请求。
GET http://localhost:8181/api/user
返回参数为:
{"id":1,"name":"xuwujing","age":18}
示例图:
可以看到程序正常返回,并没有因自定义的全局异常而影响。
然后我们再来测试下自定义的异常是否能够被正确的捕获并处理。
使用POST方式进行请求
POST http://localhost:8181/api/user
Body参数为:
{"id":1,"age":18}
返回参数为:
{"code":"-1","message":"用户姓名不能为空!","result":null}
示例图:
可以看出将我们抛出的异常进行数据封装,然后将异常返回出来。
然后我们再来测试下空指针异常是否能够被正确的捕获并处理。在自定义全局异常中,我们除了定义空指针的异常处理,也定义最高级别之一的Exception异常,那么这里发生了空指针异常之后,它是回优先使用哪一个呢?这里我们来测试下。
使用PUT方式进行请求。
PUT http://localhost:8181/api/user
Body参数为:
{"id":1,"age":18}
返回参数为:
{"code":"400","message":"请求的数据格式不符!","result":null}
示例图:
我们可以看到这里的的确是返回空指针的异常护理,可以得出全局异常处理优先处理子类的异常。
那么我们在来试试未指定其异常的处理,看该异常是否能够被捕获。
使用DELETE方式进行请求。
DELETE http://localhost:8181/api/user
Body参数为:
{"id":1}
返回参数为:
{"code":"500","message":"服务器内部错误!","result":null}
这里可以看到它使用了我们在自定义全局异常处理类中的Exception异常处理的方法。到这里,测试就结束了。顺便再说一下,自义定全局异常处理除了可以处理上述的数据格式之外,也可以处理页面的跳转,只需在新增的异常方法的返回处理上填写该跳转的路径并不使用ResponseBody 注解即可。 细心的同学也许发现了在GlobalExceptionHandler类中使用的是ControllerAdvice注解,而非RestControllerAdvice注解,如果是用的RestControllerAdvice注解,它会将数据自动转换成JSON格式,这种于Controller和RestController类似,所以我们在使用全局异常处理的之后可以进行灵活的选择处理。
其它
关于SpringBoot优雅的全局异常处理的文章就讲解到这里了,如有不妥,欢迎指正!
项目地址
SpringBoot全局异常的处理项目工程地址:https://github.com/xuwujing/springBoot-study/tree/master/springboot-exceptionHandler