闲话不多说,继续优化 全局统一Restful API 响应框架 做到项目通用 接口可扩展。
如果没有看前面几篇文章请先看前面几篇
SpringBoot定义优雅全局统一Restful API 响应框架
SpringBoot定义优雅全局统一Restful API 响应框架二
SpringBoot定义优雅全局统一Restful API 响应框架三
SpringBoot定义优雅全局统一Restful API 响应框架四
SpringBoot定义优雅全局统一Restful API 响应框架五
这里讲一讲最后的版本和需要修复的一些问题
1 @PostMapping("/add/UserApiCombo") 2 public R addApiCombo(@RequestBody @Validated UserApplyApiComboDto userApplyApiComboDto) { 3 userApiComboService.addApiCombo(userApplyApiComboDto); 4 return R.success(); 5 }
我们看看这个代码,有什么问题。 我们返回了统一的封装结果集R 但是后面所有的controller 都这么写不太友好。
- 返回内容这么不够明确具体
- 所有
controller这么写增加重复工作量
我们可以这么去优化:
Spirng 提供了 ResponseBodyAdvice 接口,支持在消息转换器执行转换之前,对接口的返回结果进行处理,再结合 @ControllerAdvice 注解即可轻松支持上述功能
1package cn.soboys.springbootrestfulapi.common.handler; 2 3import cn.hutool.core.bean.BeanUtil; 4import cn.hutool.core.map.MapUtil; 5import cn.soboys.springbootrestfulapi.common.error.ErrorDetail; 6import cn.soboys.springbootrestfulapi.common.resp.R; 7import lombok.extern.slf4j.Slf4j; 8import org.springframework.core.MethodParameter; 9import org.springframework.http.MediaType; 10import org.springframework.http.converter.HttpMessageConverter; 11import org.springframework.http.server.ServerHttpRequest; 12import org.springframework.http.server.ServerHttpResponse; 13import org.springframework.web.bind.annotation.ControllerAdvice; 14import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 15 16/** 17 * @author 公众号 程序员三时 18 * @version 1.0 19 * @date 2023/6/12 12:17 下午 20 * @webSite https://github.com/coder-amiao 21 * @Slf4j 22 * @ControllerAdvice 23 */ 24@Slf4j 25@ControllerAdvice 26public class ResponseResultHandler implements ResponseBodyAdvice<Object> { 27 /** 28 * supports方法: 判断是否要执行beforeBodyWrite方法, 29 * true为执行,false不执行. 30 * 通过该方法可以选择哪些类或那些方法的response要进行处理, 其他的不进行处理. 31 * 32 * @param returnType 33 * @param converterType 34 * @return 35 */ 36 @Override 37 public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { 38 return true; 39 } 40 41 /** 42 * beforeBodyWrite方法: 对response方法进行具体操作处理 43 * 实际返回结果业务包装处理 44 * 45 * @param body 46 * @param returnType 47 * @param selectedContentType 48 * @param selectedConverterType 49 * @param request 50 * @param response 51 * @return 52 */ 53 @Override 54 public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { 55 if (body instanceof R) { 56 return body; 57 } else if (body == null) { 58 return R.success(); 59 } else if (body instanceof ErrorDetail) { 60 return body; 61 } else if (body instanceof String) { 62 return body; 63 } else { 64 return R.success().data(body); 65 } 66 } 67} 68
在实际controller 返回中我们直接返回数据内容就可以了
1 @GetMapping("/home") 2 public Student home() { 3 4 Student s = new Student(); 5 s.setUserName("Tom"); 6 s.setAge(22); 7 List hobby = new ArrayList(); 8 hobby.add("抽烟"); 9 hobby.add("喝酒"); 10 hobby.add("烫头"); 11 s.setHobby(hobby); 12 s.setBalance(2229891.0892); 13 s.setIdCard("420222199811207237"); 14 return s; 15 }
我们目前版本中业务错误判断逻辑不是很友好,还需要优化,这里我们可以封装自己的业务异常 用 Assert(断言) 封装异常,让代码更优雅
符合 错误优先返回原则
正常我们业务异常代码是这样写的
1// 另一种写法 2 Order order = orderDao.selectById(orderId); 3 if (order == null) { 4 throw new IllegalArgumentException("订单不存在。"); 5 }
使用断言优化后
1 Order order = orderDao.selectById(orderId); 2 Assert.notNull(order, "订单不存在。");
两种方式一对比,是不是明显感觉第一种更优雅,第二种写法则是相对丑陋的 if {...} 代码块。那么 神奇的 Assert.notNull() 背后到底做了什么呢?
这里就是我们需要优化代码
其实很多框架都带有Assert 工具包括JAVA JDK . SpringBoot,spring 也有自己的Assert 但是不符合我们自己的异常抛出业务逻辑,这里我们可以自定义自定的Assert 工具
我们来看一下部分源码
1public abstract class Assert { 2 public Assert() { 3 } 4 5 public static void notNull(@Nullable Object object, String message) { 6 if (object == null) { 7 throw new IllegalArgumentException(message); 8 } 9 } 10}
可以看到,Assert 其实就是帮我们把 if {...} 封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。
那么我们是不是可以模仿Assert也写一个自定义断言类,不过断言失败后抛出的异常不是IllegalArgumentException 这些内置异常,而是我们自己定义的异常。
- 定义公共异常
1package cn.soboys.springbootrestfulapi.common.exception; 2 3import cn.soboys.springbootrestfulapi.common.resp.ResultCode; 4import lombok.Data; 5 6/** 7 * @author 公众号 程序员三时 8 * @version 1.0 9 * @date 2023/6/12 10:32 下午 10 * @webSite https://github.com/coder-amiao 11 */ 12@Data 13public class BaseException extends RuntimeException { 14 /** 15 * 返回码 16 */ 17 protected ResultCode resultCode; 18 /** 19 * 异常消息参数 20 */ 21 protected Object[] args; 22 23 public BaseException(ResultCode resultCode) { 24 super(resultCode.getMessage()); 25 this.resultCode = resultCode; 26 } 27 28 29 public BaseException(String code, String msg) { 30 super(msg); 31 this.resultCode = new ResultCode() { 32 @Override 33 public String getCode() { 34 return code; 35 } 36 37 @Override 38 public String getMessage() { 39 return msg; 40 } 41 42 @Override 43 public boolean getSuccess() { 44 return false; 45 } 46 47 ; 48 }; 49 } 50 51 public BaseException(ResultCode resultCode, Object[] args, String message) { 52 super(message); 53 this.resultCode = resultCode; 54 this.args = args; 55 } 56 57 public BaseException(ResultCode resultCode, Object[] args, String message, Throwable cause) { 58 super(message, cause); 59 this.resultCode = resultCode; 60 this.args = args; 61 } 62 63}
- 所有其他异常继承公共异常
1package cn.soboys.springbootrestfulapi.common.exception; 2 3 4import cn.soboys.springbootrestfulapi.common.resp.ResultCode; 5 6 7/** 8 * @author 公众号 程序员三时 9 * @version 1.0 10 * @date 2023/4/29 00:15 11 * @webSite https://github.com/coder-amiao 12 * 通用业务异常封装 13 */ 14public class BusinessException extends BaseException { 15 16 17 public BusinessException(ResultCode resultCode, Object[] args, String message) { 18 super(resultCode, args, message); 19 } 20 21 public BusinessException(ResultCode resultCode, Object[] args, String message, Throwable cause) { 22 super(resultCode, args, message, cause); 23 } 24 25} 26
- 断言业务异常类封装
1public interface Assert { 2 /** 3 * 创建异常 4 * @param args 5 * @return 6 */ 7 BaseException newException(Object... args); 8 9 /** 10 * 创建异常 11 * @param t 12 * @param args 13 * @return 14 */ 15 BaseException newException(Throwable t, Object... args); 16 17 /** 18 * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常 19 * 20 * @param obj 待判断对象 21 */ 22 default void assertNotNull(Object obj) { 23 if (obj == null) { 24 throw newException(obj); 25 } 26 } 27 28 /** 29 * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常 30 * <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作 31 * 32 * @param obj 待判断对象 33 * @param args message占位符对应的参数列表 34 */ 35 default void assertNotNull(Object obj, Object... args) { 36 if (obj == null) { 37 throw newException(args); 38 } 39 } 40}
具体使用
1/** 2 * 异常返回模拟 3 * 4 * @return 5 */ 6 @GetMapping("/exception") 7 public Student exception() { 8 Student s = null; 9 BusinessErrorCode.Sign_Error.assertNotNull(s,"secret秘钥不正确"); 10 return s; 11 }
在业务中我们可以通过这个方式直接抛出枚举异常。这样代码就简洁干净很多
代理已经更新到 github仓库脚手架项目
关注公众号,程序员三时 持续输出优质内容 希望给你带来一点启发和帮助
