SpringBoot定义优雅全局统一Restful API 响应框架六

kenx
• 阅读 334

闲话不多说,继续优化 全局统一Restful API 响应框架 做到项目通用 接口可扩展。

如果没有看前面几篇文章请先看前面几篇

SpringBoot定义优雅全局统一Restful API 响应框架

SpringBoot定义优雅全局统一Restful API 响应框架二

SpringBoot定义优雅全局统一Restful API 响应框架三

SpringBoot定义优雅全局统一Restful API 响应框架四

SpringBoot定义优雅全局统一Restful API 响应框架五

这里讲一讲最后的版本和需要修复的一些问题

 @PostMapping("/add/UserApiCombo")
    public R addApiCombo(@RequestBody @Validated UserApplyApiComboDto userApplyApiComboDto) {
        userApiComboService.addApiCombo(userApplyApiComboDto);
        return R.success();
    }

我们看看这个代码,有什么问题。 我们返回了统一的封装结果集R 但是后面所有的controller 都这么写不太友好。

  1. 返回内容这么不够明确具体
  2. 所有controller 这么写增加重复工作量

我们可以这么去优化:

Spirng 提供了 ResponseBodyAdvice 接口,支持在消息转换器执行转换之前,对接口的返回结果进行处理,再结合 @ControllerAdvice 注解即可轻松支持上述功能

package cn.soboys.springbootrestfulapi.common.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.soboys.springbootrestfulapi.common.error.ErrorDetail;
import cn.soboys.springbootrestfulapi.common.resp.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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 公众号 程序员三时
 * @version 1.0
 * @date 2023/6/12 12:17 下午
 * @webSite https://github.com/coder-amiao
 * @Slf4j
 * @ControllerAdvice
 */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    /**
     * supports方法: 判断是否要执行beforeBodyWrite方法,
     * true为执行,false不执行.
     * 通过该方法可以选择哪些类或那些方法的response要进行处理, 其他的不进行处理.
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * beforeBodyWrite方法: 对response方法进行具体操作处理
     * 实际返回结果业务包装处理
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof R) {
            return body;
        } else if (body == null) {
            return R.success();
        } else if (body instanceof ErrorDetail) {
            return body;
        } else if (body instanceof String) {
            return body;
        } else {
            return R.success().data(body);
        }
    }
}

在实际controller 返回中我们直接返回数据内容就可以了

    @GetMapping("/home")
    public Student home() {

        Student s = new Student();
        s.setUserName("Tom");
        s.setAge(22);
        List hobby = new ArrayList();
        hobby.add("抽烟");
        hobby.add("喝酒");
        hobby.add("烫头");
        s.setHobby(hobby);
        s.setBalance(2229891.0892);
        s.setIdCard("420222199811207237");
        return s;
    }

我们目前版本中业务错误判断逻辑不是很友好,还需要优化,这里我们可以封装自己的业务异常 用 Assert(断言) 封装异常,让代码更优雅

符合 错误优先返回原则

正常我们业务异常代码是这样写的

// 另一种写法
        Order order = orderDao.selectById(orderId);
        if (order == null) {
            throw new IllegalArgumentException("订单不存在。");
        }

使用断言优化后

 Order order = orderDao.selectById(orderId);
 Assert.notNull(order, "订单不存在。");

两种方式一对比,是不是明显感觉第一种更优雅,第二种写法则是相对丑陋的 if {...} 代码块。那么 神奇的 Assert.notNull() 背后到底做了什么呢?

这里就是我们需要优化代码

其实很多框架都带有Assert 工具包括JAVA JDK . SpringBoot,spring 也有自己的Assert 但是不符合我们自己的异常抛出业务逻辑,这里我们可以自定义自定的Assert 工具

我们来看一下部分源码

public abstract class Assert {
    public Assert() {
    }

    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
}

可以看到,Assert 其实就是帮我们把 if {...} 封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。

那么我们是不是可以模仿Assert也写一个自定义断言类,不过断言失败后抛出的异常不是IllegalArgumentException 这些内置异常,而是我们自己定义的异常。

  1. 定义公共异常
    package cn.soboys.springbootrestfulapi.common.exception;
    

import cn.soboys.springbootrestfulapi.common.resp.ResultCode; import lombok.Data;

/**

  • @author 公众号 程序员三时

  • @version 1.0

  • @date 2023/6/12 10:32 下午

  • @webSite https://github.com/coder-amiao

  • / @Data public class BaseException extends RuntimeException { /**

    • 返回码

    • / protected ResultCode resultCode; /**

    • 异常消息参数

    • / protected Object[] args;

      public BaseException(ResultCode resultCode) { super(resultCode.getMessage()); this.resultCode = resultCode; }

public BaseException(String code, String msg) {
    super(msg);
    this.resultCode = new ResultCode() {
        @Override
        public String getCode() {
            return code;
        }

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

        @Override
        public boolean getSuccess() {
            return false;
        }

        ;
    };
}

public BaseException(ResultCode resultCode, Object[] args, String message) {
    super(message);
    this.resultCode = resultCode;
    this.args = args;
}

public BaseException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
    super(message, cause);
    this.resultCode = resultCode;
    this.args = args;
}

}

2. 所有其他异常继承公共异常

```java
package cn.soboys.springbootrestfulapi.common.exception;


import cn.soboys.springbootrestfulapi.common.resp.ResultCode;


/**
 * @author 公众号 程序员三时
 * @version 1.0
 * @date 2023/4/29 00:15
 * @webSite https://github.com/coder-amiao
 * 通用业务异常封装
 */
public class BusinessException extends BaseException {


    public BusinessException(ResultCode resultCode, Object[] args, String message) {
        super(resultCode, args, message);
    }

    public BusinessException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
        super(resultCode, args, message, cause);
    }

}
  1. 断言业务异常类封装
public interface Assert {
    /**
     * 创建异常
     * @param args
     * @return
     */
    BaseException newException(Object... args);

    /**
     * 创建异常
     * @param t
     * @param args
     * @return
     */
    BaseException newException(Throwable t, Object... args);

    /**
     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
     *
     * @param obj 待判断对象
     */
    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }

    /**
     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
     * <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作
     *
     * @param obj 待判断对象
     * @param args message占位符对应的参数列表
     */
    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }
}

具体使用

/**
     * 异常返回模拟
     *
     * @return
     */
    @GetMapping("/exception")
    public Student  exception() {
        Student s = null;
        BusinessErrorCode.Sign_Error.assertNotNull(s,"secret秘钥不正确");
        return s;
    }

在业务中我们可以通过这个方式直接抛出枚举异常。这样代码就简洁干净很多

代理已经更新到 github仓库脚手架项目

关注公众号,程序员三时 持续输出优质内容 希望给你带来一点启发和帮助

点赞
收藏
评论区
推荐文章
kenx kenx
3年前
SpringBoot 默认json解析器详解和字段序列化自定义
前言在我们开发项目API接口的时候,一些没有数据的字段会默认返回NULL,数字类型也会是NULL,这个时候前端希望字符串能够统一返回空字符,数字默认返回0,那我们就需要自定义json序列化处理SpringBoot默认的json解析方案我们知道在springboot中有默认的json解析器,SpringBoot中默认使用的Json解析技术框架是ja
kenx kenx
3年前
Spring Boot 无侵入式 实现RESTful API接口统一JSON格式返回
前言现在我们做项目基本上中大型项目都是选择前后端分离,前后端分离已经成了一个趋势了,所以总这样·我们就要和前端约定统一的api接口返回json格式,这样我们需要封装一个统一通用全局模版api返回格式,下次再写项目时候直接拿来用就可以了约定JSON格式一般我们和前端约定json格式是这样的json"code":200,"message
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架
假如现在有一个Java项目,老板让你做项目组长,定义项目基础框架,系统技术架构选型,你应该如何设计一个规范的统一的RestfulAPI响应框架呢思考目前项目开发,都是基于前后端分离模式开发的,基于后端模板引擎那一套,可能已经不适用一些项目开发流程,和当下开
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架二
这里解决之前留下来的问题,当程序没有正常返回时候就是程序由于运行时异常导致的结果,有些异常我们可,能无法提前预知,不能正常走到我们return的R对象返回。这个时候该如何处理在SpringBoot中,可以使用@ControllerAdvice注解来启用全局
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架三
我们目前已经设计出了,包含全局响应,异常错误响应进行了统一返回。但是错误内容我们设计的比较模糊统一,还可以进行细化这样更有利于定位错误当我们需要调用Http接口时,无论是在Web端还是移动端,都有可能遇到各种错误,例如参数缺失、类型错误、系统错误等。为了规
Stella981 Stella981
3年前
Spring Boot @ControllerAdvice+@ExceptionHandler处理controller异常
需求:  1.springboot 项目restful 风格统一放回json  2.不在controller写trycatch代码块简洁controller层  3.对异常做统一处理,同时处理@Validated校验器注解的异常方法:  @ControllerAdvice注解定义全局异常处理类@ControllerAdvice
Wesley13 Wesley13
3年前
.NET Core AutoWrapper 自定义响应输出
前言AutoWrapper是一个简单可自定义全局异常处理程序和ASP.NETCoreAPI响应的包装。他使用ASP.NETCoremiddleware拦截传入的HTTP请求,并将最后的结果使用统一的格式来自动包装起来.目的主要是让我们更多的关注业务特定的代码要求,并让包装器自动处理HTTP响应。这可以在构建API时加快开发时间,同时为HTT
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架四
好代码是优化出来的,不是写出来的!!如果没看前面文章,可以先看前面几篇SpringBoot定义优雅全局统一RestfulAPI响应框架
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架五
闲话不多说,继续优化全局统一RestfulAPI响应框架做到项目通用接口可扩展。如果没有看前面几篇文章请先看前面几篇这里解决上一篇留下问题如何实现接口错误国际化。还有上一篇错误提示也不是很友好我们可以在进一步抽象出通用异常接口。异常信息应由固定异常编码信息
kenx kenx
1年前
SpringBoot定义优雅全局统一Restful API 响应框架完结撒花篇封装starter组件
之前我们已经,出了一些列文章。讲解如何封统一全局响应RestfulAPI。感兴趣的可以看我前面几篇文章(整个starter项目发展史)后续我萌生里新的想法,SpringBoot不是提供了自己的starter。我们也可以自定义starter吗,于是我定义了r