闲话不多说,继续优化 全局统一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
都这么写不太友好。
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 这些内置异常,而是我们自己定义的异常。
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; } }
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); } }
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仓库脚手架项目
关注公众号,程序员三时 持续输出优质内容 希望给你带来一点启发和帮助