SpringBoot-(二十一)高级统一结果的封装和全局异常的处理

本文最后更新于:September 2, 2022 pm

SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

目录

统一返回结果类

返回状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.tothefor.DBTest;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
* 状态码
* @Author DragonOne
* @Date 2022/8/21 11:39
* @墨水记忆 www.tothefor.com
*/

@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public enum RCode {

/**
* 成功
*/
SUCCESS(200, "成功"),

/**
* 失败
*/
FAIL(500, "失败"),

;

/**
* @Author DragonOne
* @Date 2022/8/21 11:56
* @墨水记忆 www.tothefor.com
* @属性 statusCode
* @作用 返回结果状态码
*/
private Integer code;

/**
* @Author DragonOne
* @Date 2022/8/21 11:56
* @墨水记忆 www.tothefor.com
* @属性 statusMessage
* @作用 提示信息
*/
private String message;


}

统一返回结果类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.tothefor.DBTest;

import lombok.*;

import java.io.Serializable;


/**
* 统一结果封装类
*
* @Author DragonOne
* @Date 2022/8/21 11:39
* @墨水记忆 www.tothefor.com
*/

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class R<T> implements Serializable {

/**
* 对应 RCode 状态码中的属性
*/
private Boolean success;
private Integer code;
private String message;

/**
* @Author DragonOne
* @Date 2022/8/21 12:04
* @墨水记忆 www.tothefor.com
* @属性 timestamp
* @作用 接口请求时间
*/
private long timestamp;

/**
* @Author DragonOne
* @Date 2022/8/21 11:59
* @墨水记忆 www.tothefor.com
* @属性 data
* @作用 携带后台传入的数据
*/
private T data;

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 13:04
* @墨水记忆 www.tothefor.com
* @方法 SUCCESS
* @作用 成功,无数据
* @参数说明
*/
public static <T> R<T> SUCCESS() {
return SUCCESS(null);
}

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 12:11
* @墨水记忆 www.tothefor.com
* @方法 SUCCESS
* @作用 成功,有数据
* @参数说明
*/
public static <T> R<T> SUCCESS(T data) {
return R.<T>builder().data(data)
.success(true)
.code(RCode.SUCCESS.getCode())
.message(RCode.SUCCESS.getMessage())
.timestamp(System.currentTimeMillis())
.build();
}

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 12:38
* @墨水记忆 www.tothefor.com
* @方法 FAIL
* @作用 失败,无数据
* @参数说明
*/
public static <T extends Serializable> R<T> FAIL() {
return FAIL(null);
}

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 12:38
* @墨水记忆 www.tothefor.com
* @方法 FAIL
* @作用 失败,有数据
* @参数说明
*/
public static <T> R<T> FAIL(T data) {
return R.<T>builder().data(data)
.success(false)
.code(RCode.FAIL.getCode())
.message(RCode.FAIL.getMessage())
.timestamp(System.currentTimeMillis())
.build();
}

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 17:19
* @墨水记忆 www.tothefor.com
* @方法 FAIL
* @作用 更具不同的场景,自定义不同的状态
* @参数说明 自定义错误编码,自定义错误提示信息,无数据
*/
public static <T> R<T> FAIL(Integer code, String msg) {
return FAIL(code, msg, null);
}

/**
* @return
* @Author DragonOne
* @Date 2022/8/21 17:19
* @墨水记忆 www.tothefor.com
* @方法 FAIL
* @作用 更具不同的场景,自定义不同的状态
* @参数说明 自定义错误编码,自定义错误提示信息,有数据
*/
public static <T> R<T> FAIL(Integer code, String msg, T data) {
return R.<T>builder().data(data)
.success(false)
.code(code)
.message(msg)
.timestamp(System.currentTimeMillis())
.build();
}

}

全局异常处理

异常状态码接口(*)

这作为一个异常状态码的父类,如果需要自定义异常状态码,建议实现此接口。(面向接口编程)当然了,也可以根据自己的需要不要此接口也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.tothefor.DBTest;

/**
* @Author DragonOne
* @Date 2022/8/21 13:05
* @墨水记忆 www.tothefor.com
*
* 全局自定义异常状态码接口,自定义异常状态码时建议单独定义枚举类实现该接口
*/
public interface BizError {

default BizError of(int code,String msg){
return new BizError() {
@Override
public int code() {
return code;
}

@Override
public String exception() {
return msg;
}
};
}


int code();
String exception();
}

自定义异常状态码

自定义异常状态码时建议单独定义枚举类实现异常状态码接口 (BizError)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.tothefor.DBTest;

import lombok.AllArgsConstructor;

/**
* @Author DragonOne
* @Date 2022/8/21 13:06
* @墨水记忆 www.tothefor.com
*
* 具体自定义异常状态码,可根据自行实现BizError进行自定义异常状态码
*/
@AllArgsConstructor
public enum BizErrors implements BizError {
ERROR_SYSTEM(1000000,"系统异常"),
;

/**
* 异常码
*/
int code;
/**
* 异常信息
*/
String exception;

@Override
public int code() {
return code;
}

@Override
public String exception() {
return exception;
}
}

自定义异常处理类

自定义异常通常是继承RuntimeException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.tothefor.DBTest;

import lombok.Data;

/**
* @Author DragonOne
* @Date 2022/8/21 13:03
* @墨水记忆 www.tothefor.com
* 自定义异常
*/
@Data
public class BizErrorException extends RuntimeException{
private int code;
private String exception;

public BizErrorException(BizError errors) {
this.code = errors.code();
this.exception = errors.exception();
}
}

通过提供的异常状态码将其封装成一个自定义异常类。

添加自定义异常处理到全局异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tothefor.DBTest;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

/**
* @Author DragonOne
* @Date 2022/8/20 14:17
* @墨水记忆 www.tothefor.com
*/

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BizErrorException.class)
public BizErrorException handler(BizErrorException e, HttpServletRequest request){
return e;
}
}

需要注意的是:这里的异常捕获返回值根据个人情况!!但是在后面处理判断Controller类的返回结果时(beforeBodyWrite),需要修改为这里的返回值!

统一封装Controller结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.tothefor.DBTest;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
* @Author DragonOne
* @Date 2022/8/21 12:03
* @墨水记忆 www.tothefor.com
*/
@ControllerAdvice(basePackages = "com.tothefor.DBTest")
public class ResponseBody implements ResponseBodyAdvice<Object> {

@Autowired
private ObjectMapper objectMapper;

@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true; // 表示支持
}

@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o==null){
return R.SUCCESS();
}
if(o instanceof BizErrorException){ //如果是自定义异常,即前面异常捕获时的返回值
BizErrorException eO = (BizErrorException) o;
return R.FAIL(eO.getCode(),eO.getException());
}
if(o instanceof String){ // String 类型需要特殊处理
return objectMapper.writeValueAsString(R.SUCCESS(o));
}
return R.SUCCESS(o);
}
}

ControllerAdvice中的值表示要拦截的Controller,必须要写,否则不会生效!!!

实现ResponseBodyAdvice接口,其实是对加了@RestController(也就是@Controller+@ResponseBody)注解的处理器将要返回的值进行增强处理。其实也就是采用了AOP的思想,对返回值进行一次修改。

使用示例

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/gete")
public int gete(){
int i =0;
try {
i/=0;
}catch (Exception e){
throw new BizErrorException(BizErrors.ERROR_SYSTEM);
}
return i;
}