写点什么

瞧瞧人家用 SpringBoot 写的后端 API 接口,那叫一个优雅

作者:程序知音
  • 2022 年 7 月 02 日
  • 本文字数:3881 字

    阅读完需:约 13 分钟

日常工作中,我们开发接口时,一般都会涉及到参数校验、异常处理、封装结果返回等处理。如果每个后端开发在参数校验、异常处理等都是各写各的,没有统一处理的话,代码就不优雅,也不容易维护。所以,作为一名合格的后端开发工程师,我们需要统一校验参数,统一异常处理、统一结果返回,让代码更加规范、可读性更强、更容易维护。


使用注解,优雅进行参数校验统一结果返回统一异常处理唠叨几句


  1. 使用注解,统一参数校验假设小田螺实现一个注册用户的功能,在 controller 层,他会先进行校验参数,如下:


@RestController@RequestMappingpublic class UserController {


@RequestMapping("addUser")public String addUser(UserParam userParam) {
if (StringUtils.isEmpty(userParam.getUserName())) { return "用户名不能为空"; } if (StringUtils.isEmpty(userParam.getPhone())) { return "手机号不能为空"; } if (userParam.getPhone().length() > 11) { return "手机号不能超过11"; } if (StringUtils.isEmpty(userParam.getEmail())) { return "邮箱不能为空"; }
//省略其他参数校验
//todo 插入用户信息表 return "SUCCESS";}
复制代码


}以上代码有什么问题嘛?其实没什么问题,就是校验有点辣眼睛。正常的添加用户业务还没写,参数校验就一大堆啦。假设后来,小田螺又接了一个需求:编辑用户信息。实现编辑用户信息前,也是先校验信息,如下:


@RequestMapping("editUser")public String editUser(UserParam userParam) {


if (StringUtils.isEmpty(userParam.getUserName())) {    return "用户名不能为空";}if (StringUtils.isEmpty(userParam.getPhone())) {    return "手机号不能为空";}if (userParam.getPhone().length() > 11) {    return "手机号不能超过11";}
if (StringUtils.isEmpty(userParam.getEmail())) { return "邮箱不能为空";}
//省略其他参数校验
//todo 编辑用户信息表return "SUCCESS";
复制代码


}我们可以使用注解的方式,来进行参数校验,这样代码更加简洁,也方便统一管理。实际上, spring boot 有个 validation 的组件,我们可以拿来即用。引入这个包即可:


<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>引入包后,参数校验就非常简洁啦,如下:


public class UserParam {


@NotNull(message = "用户名不能为空")private String userName;
@NotNull(message = "手机号不能为空")@Max(value = 11)private String phone;
@NotNull(message = "邮箱不能为空")private String email;
复制代码


然后在 UserParam 参数对象中,加入 @Validated 注解哈,把错误信息接收到 BindingResult 对象,代码如下:


@RequestMapping("addUser")public String addUser(@Validated UserParam userParam, BindingResult result) {        List<FieldError> fieldErrors = result.getFieldErrors();    if (!fieldErrors.isEmpty()) {        return fieldErrors.get(0).getDefaultMessage();    }
//todo 插入用户信息表 return "SUCCESS";}
复制代码


  1. 接口统一响应对象返回如果你在你们项目代码中,看到 controller 层报文返回结果,有这样的:


@RequestMapping("/hello")public String getStr(){return "hello,捡田螺的小男孩";}


//返回 hello,捡田螺的小男孩也有这样的:


@RequestMapping("queryUser")public UserVo queryUser(String userId) {return new UserVo("666", "捡田螺的小男孩");}//返回:{"userId":"666","name":"捡田螺的小男孩"}显然,如果接口返回结果不统一,前端处理就不方便,我们代码也不好维护。再比如小田螺喜欢用 Result 处理结果,大田螺喜欢用 Response 处理结果,可以想象一下,这些代码有多乱。


所以作为后端开发,我们项目的响应结果,需要统一标准的返回格式。一般一个标准的响应报文对象,都有哪些属性呢?


code :响应状态码 message :响应结果描述 data:返回的数据响应状态码一般用枚举表示哈:


public enum CodeEnum {


/**操作成功**/SUCCESS("0000","操作成功"),/**操作失败**/ERROR("9999","操作失败"),;
/** * 自定义状态码 **/private String code;/**自定义描述**/private String message;
CodeEnum(String code, String message){ this.code = code; this.message = message;}
public String getCode() { return code;}public String getMessage() { return message;}
复制代码


}因为返回的数据类型不是确定的,我们可以使用泛型,如下:


/**


  • @author 捡田螺的小男孩

  • @param <T>*/public class BaseResponse<T> {

  • /**

  • 响应状态码(0000 表示成功,9999 表示失败*/private String code;

  • /**

  • 响应结果描述*/private String message;

  • /**

  • 返回的数据*/private T data;

  • /**

  • 成功返回

  • @param data

  • @param <T>

  • @return*/public static <T> BaseResponse<T> success(T data) {BaseResponse<T> response= new BaseResponse<>();response.setCode(CodeEnum.SUCCESS.getCode());response.setMessage(CodeEnum.SUCCESS.getMessage());response.setData(data);return response;}

  • /**

  • 失败返回

  • @param code

  • @param message

  • @param <T>

  • @return*/public static <T> BaseResponse<T> fail(String code, String message) {BaseResponse<T> response = new BaseResponse<>();response.setCode(code);response.setMessage(message);return response;}

  • public void setCode(String code) {this.code = code;}

  • public void setMessage(String message) {this.message = message;}

  • public void setData(T data) {this.data = data;}}有了统一的响应体,我们就可以优化一下 controller 层的代码啦:


@RequestMapping("/hello")public BaseResponse<String> getStr(){return BaseResponse.success("hello,捡田螺的小男孩");}//output{"code":"0000","message":"操作成功","data":"hello,捡田螺的小男孩"}


@RequestMapping("queryUser")public BaseResponse<UserVo> queryUser(String userId) {return BaseResponse.success(new UserVo("666", "捡田螺的小男孩"));}//output{"code":"0000","message":"操作成功","data":{"userId":"666","name":"捡田螺的小男孩"}}3. 统一异常处理日常开发中,我们一般都是自定义统一的异常类,如下:


public class BizException extends RuntimeException {


private String retCode;
private String retMessage;
public BizException() { super();}
public BizException(String retCode, String retMessage) { this.retCode = retCode; this.retMessage = retMessage;}
public String getRetCode() { return retCode;}
public String getRetMessage() { return retMessage;}
复制代码


}在 controller 层,很可能会有类似代码:


@RequestMapping("/query")public BaseResponse<UserVo> queryUserInfo(UserParam userParam) {try {return BaseResponse.success(userService.queryUserInfo(userParam));} catch (BizException e) {//doSomething} catch (Exception e) {//doSomething}return BaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());}这块代码,没什么问题哈,但是如果 try...catch 太多,不是很优雅。


可以借助注解 @RestControllerAdvice,让代码更优雅。@RestControllerAdvice 是一个应用于 Controller 层的切面注解,它一般配合 @ExceptionHandler 注解一起使用,作为项目的全局异常处理。我们来看下 demo 代码哈。


还是原来的 UserController,和一个会抛出异常的 userService 的方法,如下:


@RestControllerpublic class UserController {


@Autowiredprivate UserService userService;
@RequestMapping("/query")public BaseResponse<UserVo> queryUserInfo1(UserParam userParam) { return BaseResponse.success(userService.queryUserInfo(userParam));}
复制代码


}


@Servicepublic class UserServiceImpl implements UserService {//抛出异常 @Overridepublic UserVo queryUserInfo(UserParam userParam) throws BizException {throw new BizException("6666", "测试异常类");}}我们再定义一个全局异常处理器,用 @RestControllerAdvice 注解,如下:


@RestControllerAdvice(annotations = RestController.class)public class ControllerExceptionHandler {}我们有想要拦截的异常类型,比如想拦截 BizException 类型,就新增一个方法,使用 @ExceptionHandler 注解修饰,如下:


@RestControllerAdvice(annotations = RestController.class)public class ControllerExceptionHandler {


@ExceptionHandler(BizException.class)@ResponseBodypublic BaseResponse<Void> handler(BizException e) {    System.out.println("进入业务异常"+e.getRetCode()+e.getRetMessage());    return BaseResponse.fail(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMessage());}
复制代码


}唠叨几句本文大家学到了哪些知识呢?


为了写出更优雅、更简洁、更容易维护的代码,我们需要统一参数校验、统一响应对象返回、统一异常处理参数校验更简洁,可以使用注解实现。如何统一响应对象返回,一般要包括状态码、描述信息、返回数据。Controller 层如何统一全局异常处理?@RestControllerAdvice+@ExceptionHandler 进阶篇?大家可以自己实现自定义注解哈,也建议去看看 @RestControllerAdvice 实现原理,它其实就是一个切面注解,看下它的源码即可。

用户头像

程序知音

关注

还未添加个人签名 2022.06.25 加入

还未添加个人简介

评论

发布
暂无评论
瞧瞧人家用SpringBoot写的后端API接口,那叫一个优雅_Java_程序知音_InfoQ写作社区