SpringBoot中如何统一接口返回与全局异常处理详解
作者:l拉不拉米 发布时间:2021-08-11 02:22:35
标签:springboot,接口,返回
目录
背景
统一接口返回
定义API返回码枚举类
定义正常响应的API统一返回体
定义异常响应的API统一返回体
编写包装返回结果的自定义注解
定义返回结果 *
WebMvc配置类 * 注册者添加返回结果 *
编写响应体处理器
接口调用
测试结果
全局异常处理
编写自定义异常基类
编写自定义业务异常类
定义全局异常处理类
接口调用
测试结果
总结
背景
在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。维护一套完善且规范的接口是非常有必要的, 这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。
使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。
因此,我们需要定义一个统一的全局异常,在Controller捕获所有异常,并且做适当处理,并作为一种结果返回。
统一接口返回
定义API返回码枚举类
public enum ResultCode {
/* 成功状态码 */
SUCCESS(200, "成功"),
/* 错误状态码 */
NOT_FOUND(404, "请求的资源不存在"),
INTERNAL_ERROR(500, "服务器内部错误"),
PARAMETER_EXCEPTION(501, "请求参数校验异常"),
/* 业务状态码 */
USER_NOT_EXIST_ERROR(10001, "用户不存在"),
;
private Integer code;
private String message;
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
}
定义正常响应的API统一返回体
@Data
public class Result<T> implements Serializable {
private Integer code;
private String message;
private boolean success = true;
private T data;
@JsonIgnore
private ResultCode resultCode;
private Result() {
}
public void setResultCode(ResultCode resultCode) {
this.resultCode = resultCode;
this.code = resultCode.code();
this.message = resultCode.message();
}
public Result(ResultCode resultCode, T data) {
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
}
定义异常响应的API统一返回体
@Data
public class ErrorResult implements Serializable {
private Integer code;
private String message;
private boolean success = false;
@JsonIgnore
private ResultCode resultCode;
public static ErrorResult error() {
ErrorResult result = new ErrorResult();
result.setResultCode(ResultCode.INTERNAL_ERROR);
return result;
}
public static ErrorResult error(String message) {
ErrorResult result = new ErrorResult();
result.setCode(ResultCode.INTERNAL_ERROR.code());
result.setMessage(message);
return result;
}
public static ErrorResult error(Integer code, String message) {
ErrorResult result = new ErrorResult();
result.setCode(code);
result.setMessage(message);
return result;
}
public static ErrorResult error(ResultCode resultCode, String message) {
ErrorResult result = new ErrorResult();
result.setResultCode(resultCode);
result.setMessage(message)
return result;
}
}
编写包装返回结果的自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) //作用于方法和类(接口)上
@Documented
public @interface ResponseResult {
}
定义返回结果 *
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
/* 使用统一返回体的标识 */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 正在处理请求的方法bean
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取当前类
final Class<?> clazz = handlerMethod.getBeanType();
// 获取当前方法
final Method method = handlerMethod.getMethod();
// 判断是否在类对象上加了注解
if (clazz.isAnnotationPresent(ResponseResult.class)) {
// 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断
request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class));
}
// 判断是否在方法上加了注解
else if (method.isAnnotationPresent(ResponseResult.class)) {
// 设置该请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断
request.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
WebMvc配置类 * 注册者添加返回结果 *
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加自定义 *
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/**");
}
}
编写响应体处理器
/**
* 统一处理响应体,用Result.success静态方法包装,
* 在API接口使用时就可以直接返回原始类型
*/
@RestControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/* 使用统一返回体的标识 */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(sra).getRequest();
ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION);
// 判断返回体是否需要处理
return responseResult != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 异常响应体则直接返回code+message的消息体
if (body instanceof ErrorResult) {
return body;
}
// 正常响应体则返回Result包装的code+message+data的消息体
return Result.success(body);
}
}
接口调用
@Api("用户管理")
@RestController
@RequestMapping("user")
@ResponseResult // 作用于类上,对所有接口有效
public class UserController {
@Autowired
private UserService userService;
@ResponseResult // 作用于方法上
@ApiOperation("根据ID查询用户")
@GetMapping("one")
public User selectOne(Long id) {
// 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,
// 直接返回原始类型即可,代码更简洁
return this.userService.queryById(id);
}
@ResponseResult
@ApiOperation("查询所有用户")
@GetMapping("all")
public List<User> selectAll(Page page) {
// 由于在ResponseResultHandler中已经统一将返回数据用Result.success包装了,
// 直接返回原始类型即可,代码更简洁
return this.userService.queryAllByLimit(page);
}
}
测试结果
全局异常处理
编写自定义异常基类
@Data
public class BaseException extends RuntimeException {
private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code();
private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message();
private Integer code;
private String message;
public BaseException() {
super(BASE_EXCEPTION_MESSAGE);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message) {
super(message);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(ResultCode resultCode) {
super(resultCode.message());
this.code = resultCode.code();
this.message = resultCode.message();
}
public BaseException(Throwable cause) {
super(cause);
this.code = BASE_EXCEPTION_CODE;
this.message = BASE_EXCEPTION_MESSAGE;
}
public BaseException(String message, Throwable cause) {
super(message, cause);
this.code = BASE_EXCEPTION_CODE;
this.message = message;
}
public BaseException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BaseException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
}
编写自定义业务异常类
public class BizException extends BaseException {
public BizException(ResultCode resultCode) {
super(resultCode);
}
}
定义全局异常处理类
通过@ExceptionHandler注解来统一处理某一类异常
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 统一处理自定义基础异常
*/
@ExceptionHandler(BaseException.class)
public ErrorResult baseException(BaseException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/**
* 统一处理自定义业务异常
*/
@ExceptionHandler(BizException.class)
public ErrorResult bizException(BizException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/**
* 统一处理非自定义异常外的所有异常
*/
@ExceptionHandler(Exception.class)
public ErrorResult handleException(Exception e) {
log.error(e.getMessage(), e);
return ErrorResult.error(e.getMessage());
}
/**
* 兼容Validation校验框架:忽略参数异常处理器
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ApiResult<String> parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数 " + e.getParameterName() + " 不能为空");
}
/**
* 兼容Validation校验框架:缺少请求体异常处理器
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "参数体不能为空");
}
/**
* 兼容Validation校验框架:参数效验异常处理器
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
// 获取异常信息
BindingResult exceptions = e.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
FieldError fieldError = (FieldError) errors.get(0);
return ErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage());
}
}
return ErrorResult.error(PARAMETER_EXCEPTION, "请求参数校验异常");
}
}
接口调用
@ResponseResult
@GetMapping
public User update() {
// 非自定义的运行时异常
long id = 10 / 0;
return userService.queryById(id);
}
@ResponseResult
@PostMapping
public User insert() {
// 抛出自定义的基础异常
throw new BaseException();
}
@ResponseResult
@DeleteMapping
public boolean delete() {
// 抛出自定义的业务异常
throw new BizException(USER_NOT_EXIST_ERROR);
}
测试结果
来源:https://juejin.cn/post/7002926832160866318


猜你喜欢
- 一、概述1、事务ACID特性事务将一系列的工作视为一个工作单元,它具有 ACID 特性:A:Atomicity 不可分性 也就是说
- 方法参数public String listFireEvent(@Valid FireSearch fireSearch, Ht
- 先为啥要纯手工打造呢,因为对方是用C++做的,我按照他们给出的WSDL实现了一个WebService,结果他们完全不能调用。具体是他们调用的
- 相应的类库可在我的资源页面中找到,关于类成员的说明可通过对象浏览器查看函数说明Imports BitOperatorLibrary.Shif
- 项目场景:适用于接口数据敏感信息,比如 明文传输姓名、居住地址、手机号等信息,如果存在明文传输敏感数据问题、及数据泄漏风险,则可使用此方法加
- 微信分享接口的java开发的一些小步骤,具体内容如下1.配置接口信息进行验证代码如下: /** * 访问没认证的地
- 前言作为一个开发者,日常会接触到很多优秀的软件,其实,或多或少会有这样的想法,我能不能开发一个自己软件,甚至办公软件都希望是Markdown
- Android 自定义组件成JAR包的实现方法,这里对自己实现的Android View 组件进行JAR 包的处理。
- 本片shader实现的效果是模型腐蚀消失,且腐蚀的边缘大小可以调、颜色可调。效果图如下:设置面板如下:使用时需要给ClipMask参数给一张
- 一、数组(Array)数组具有以下的特点:数组属于线性结构,在内存中是连续存放的。数组的元素类型必须相同。数组可以直接通过下标访问。数组的查
- 在笔试编程过程中,关于数据的读取如果迷迷糊糊,那后来的编程即使想法很对,实现很好,也是徒劳,于是在这里认真总结了Java Scanner 类
- 一,问题采取eureka集群、客户端通过Ribbon调用服务,Ribbon端报下列异常java.net.UnknownHostExcepti
- 一、前言在软件开发中,经常用到设置这样的功能,如果设置中的功能不多,用 Json、XML 这样的数据结构存储非常的麻烦,一个字段的读写,就要
- 安装JDK 向导进行相关参数设置。如图: 正在安装程序的相关功能,如图: 选择安装的路径,可以自定义,也可以默认路径。如图: 成功安装之
- 在网上看了比较多的关于Tab的教程,发现都很杂乱。比较多的用法是用TitlePagerTabStrip和ViewPaper。不过TitleP
- 1. 前言老板说,明天甲方要来看产品,你得造点数据,而且数据必须是“真”的,演示效果要好看一些,这样他才会买我们的产品,我好明年给你换个嫂子
- 性能优化的帮助工具:MAT,Memory Monitor(属于AndroidMonitor中一个模块),HeapTool(查看堆
- Recyclerview现在基本已经替代Listview了,RecyclerView也越来越好用了 当我们有实现条目的拖拽排序和
- 以前使用HttpServletResponse可以通过输出流的方式来向前台输出图片。现在大部分都是使用springboot,在使用sprin
- 目录1、在异常处理中,如释放资源,关闭数据库、关闭文件应由( )语句来完成。2、如下Java语句 double x=2.0; int y=4