在Spring中全局处理异常

随着业务的发展,在每一个业务模块里面都可能会出现一些自定义的通用异常,例如·验证失败权限不足等等 ,这些自定义的异常很可能会被所有模块公用,在代码里面,最常见的一种写法是在每一个方法里面进行捕获,然后在Controller里面进行catch,最后进行相应处理

常见写法

第一种写法:

这是一个常规的写法,每一个方法都处理自己的特定异常
Controller层

1
2
3
4
5
6
7
8
9
10
11
public void login(){
try{
//逻辑处理
}catch(AuthException e){
XXX
}catch(CrsfException e){
XXX
}catch(Exception e){
XXX
}
}

这样的代码如果分布在不同的Controller里面,将会是一种隐患。例如,有一天,需要对所有的AuthException异常都添加一个字段,用于前端的页面展示,那么此时我们就需要在代码里面找出所有的AuthException,然后再添加一些特殊的字段,如果漏掉了几个,就会引起一些bug。

第二种写法:

第二种写法几乎和第一种一样,不过不同之处在于第二种写法是编写了一个公共的处理方法.

Controller层

1
2
3
4
5
6
7
8
9
10
11
12
13

@RequestMapping(value="login",methods=Request.POST)
public void login(){
try{
//逻辑处理
}catch(AuthException e){
ExceptionHandle.handleAuthException();
}catch(CrsfException e){
XXX
}catch(Exception e){
XXX
}
}

共用方法

1
2
3
4
5
public class ExceptionHandle(){
public static Object handleAuthException(){
XXX//逻辑处理
}
}

这种方法虽然比第一种更加具有共用性,但是代码一点都不整洁和便于维护。例如,现在需要再次加一个异常,那么就只能是在Controller里面再次Catch,如果是增加还好,但是一旦需要里面既包含增加又包含删除,对于维护人员,这是极易出错的。

Spring的全局处理异常

其实在Spring里面有更加优雅的处理方式,那就是全局的异常处理,对于一些常用的异常,直接在Controller里面抛出,而对于某一些方法的特定异常,则只需要自己进行捕获,然后自己进行处理

介绍

在Spring里面可以使用@RestControllerAdvice@ControllerAdvice,然后配合@ExceptionHandler进行处理,这样处理可以使的项目在整个异常处理这块十分的通用和优雅

异常处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AuthException extends RuntimeException {

public AuthException() {
super();
}

public AuthException(String message) {
super(message);
}

public AuthException(String message, Exception e) {
super(message, e);
}
}

全局的异常处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestControllerAdvice
public class ExceptionHandle {

@ExceptionHandler(AuthException.class)
public ResponseEntity<String> handleAuthException(){
ResponseEntity<String> resp = new ResponseEntity<>();
resp.setCode(201);
resp.setMessage("验证失败");
resp.setData("全局异常所抛出的异常");
return resp;
}
}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping(value = "api")
public class ExceptionController {

@Autowired
ExceptionService exceptionService;

@RequestMapping(value = "auth",method = RequestMethod.POST)
public ResponseEntity<String> testAuth(@RequestBody String param) throws AuthenticationException {
ResponseEntity<String> resp = new ResponseEntity<String>();
exceptionService.auth(param);
resp.setCode(200);
resp.setMessage("OK");
resp.setData("Controller消息");
return resp;
}
}

Service

1
2
3
4
5
6
7
8
9
@Service
public class ExceptionService {

public void auth(String param) throws AuthenticationException {
if("1".equalsIgnoreCase(param)){
throw new AuthException("非法访问");
}
}
}

测试如下:

请求api/auth,并且携带参数1

1
2
3
4
5
{
"data": "全局异常所抛出的异常",
"code": 201,
"message": "验证失败"
}

请求api/auth,并且携带参数2

1
2
3
4
5
{
"data": "Controller消息",
"code": 200,
"message": "OK"
}

增加特殊处理

这样一来,所有的AuthException都可以被统一的进行处理,而且根据业务的需要们可以在Controller增加一些特定的异常

此处以NullPointerException代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping(value = "api")
public class ExceptionController {

@Autowired
ExceptionService exceptionService;

@RequestMapping(value = "auth",method = RequestMethod.POST)
public ResponseEntity<String> testAuth(@RequestBody String param) throws AuthenticationException {
ResponseEntity<String> resp = new ResponseEntity<String>();
try {
exceptionService.auth(param);
}catch (NullPointerException e){
resp.setCode(400);
resp.setMessage("NUll");
resp.setData("Null");
return resp;
}
resp.setCode(200);
resp.setMessage("OK");
resp.setData("Controller消息");
return resp;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class ExceptionService {

public void auth(String param) throws AuthenticationException {
if("1".equalsIgnoreCase(param)){
throw new AuthException("非法访问");
}
if("3".equals(param)){
throw new NullPointerException();
}
}
}

再次测试

请求api/auth,并且携带参数3

1
2
3
4
5
{
"data": "Null",
"code": 400,
"message": "NUll"
}

总结

此方法虽然可以统一的处理项目里面的异常,但是对项目内的开发人员要求还是比较高的,需要一起遵守统一的开发规范

作者

Somersames

发布于

2019-01-10

更新于

2021-12-05

许可协议

评论