springboot2之优雅处理返回值

前言

最近项目组有个老项目要进行前后端分离改造,应前端同学的要求,其后端提供的返回值格式需形如

1{ 2 "status": 0, 3 "message": "success", 4 "data": { 5 6 } 7}

方便前端数据处理。要实现前端同学这个需求,其实也挺简单的,仅需做如下改造,新增一个返回对象,形如

1@Data 2@AllArgsConstructor 3@NoArgsConstructor 4@Builder 5public class Result<T> { 6 public static final int success = 0; 7 public static final int fail = 1; 8 private int status = success; 9 private String message = "success"; 10 private T data; 11 12 13} 14

然后controller改造成如下

1@RestController 2@RequestMapping("/user") 3@Slf4j 4public class UserController { 5 6 7 @Autowired 8 private UserService userService; 9 10 @PostMapping(value="/add") 11 public Result<UserDTO> addUser(@Valid UserDTO userDTO, BindingResult bindingResult){ 12 Result<UserDTO> result = new Result<>(); 13 if (bindingResult.hasErrors()){ 14 return getUserFailResult(bindingResult, result); 15 } 16 saveUser(userDTO, result); 17 18 return result; 19 20 } 21}

仅仅需要这么改造就可以满足前端同学的述求。但这边存在一个问题就是,这个项目后端接口的contoller之前都是直接返回业务bean对象,形如下

1@RestController 2@Api(tags = "用户管理") 3@Slf4j 4public class UserController { 5 6 @Autowired 7 private UserService userService; 8 9 @GetMapping(value="/get/{id}") 10 @ApiOperation("根据用户ID查找用户") 11 @ApiImplicitParam(value = "用户id",name = "id",required = true,paramType = "path") 12 public UserDTO getUserById(@PathVariable("id") Long id){ 13 UserDTO dto = userService.getUserById(id); 14 log.info("{}",dto); 15 return dto; 16 17 } 18 }

如果按上面的思路

把UserDTO改造成Result<UserDTO>

虽然可以满足需求,但问题是后端这样的接口有好几十个,按这种改法很明显工作量比较大,更重要的不符合开闭原则--对扩展开放,对修改关闭。那有没有优雅一点的处理方式呢?答案是有的,利用 @RestControllerAdvice+ResponseBodyAdvice就可以满足我们的需求

改造

1、在改造前,先简单介绍一下@RestControllerAdvice和ResponseBodyAdvice

@RestControllerAdvice

@RestControllerAdvice这个注解是spring 4.3版本之后新增的注解。用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping。利用他可以来做异常统一处理。如果使用的spring低于4.3,那可以使用@ControllerAdvice+@ResponseBody。@ControllerAdvice是spring 3.2版本后就提供的注解,其实现的功能和@RestControllerAdvice类似。 其详细的参考文档,可以查看链接@RestControllerAdvice文档以及@ControllerAdvice文档

ResponseBodyAdvice

这个是spring4.1版本之后,新增的接口。其作用是允许在执行@ResponseBody或ResponseEntity控制器方法之后但在使用HttpMessageConverter编写正文之前自定义响应。可以直接在RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver中注册实现,也可以在@ControllerAdvice或者@RestControllerAdvice中注解。其详细参考文档可以查看链接ResponseBodyAdvice文档

2、编写一个通用的响应实体

1@Data 2@AllArgsConstructor 3@NoArgsConstructor 4@Builder 5public class Result<T> { 6 public static final int success = 0; 7 public static final int fail = 1; 8 private int status = success; 9 private String message = "success"; 10 private T data; 11 12 13} 14

3、编写一个类上加上@RestControllerAdvice并实现ResponseBodyAdvice接口。用来统一处理响应值

1@RestControllerAdvice(basePackages = "com.github.lybgeek") 2@Slf4j 3public class ResponseAdvice implements ResponseBodyAdvice { 4 @Override 5 public boolean supports(MethodParameter methodParameter, Class aClass) { 6 return true; 7 } 8 9 @Override 10 public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 11 if(Objects.isNull(o)){ 12 return Result.builder().message("success").build(); 13 } 14 15 if(o instanceof Result){ 16 return o; 17 } 18 19 return Result.builder().message("success").data(o).build(); 20 } 21 22 @ExceptionHandler(Exception.class) 23 @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) 24 public Result<?> exceptionHandler(HttpServletRequest request, Exception e) { 25 log.error(e.getMessage(), e); 26 return Result.builder().message(e.getMessage()).status(Result.fail).build(); 27 } 28 29 /** 30 * 针对业务异常统一处理 31 * @param request 32 * @param bizException 33 * @return 34 */ 35 @ExceptionHandler(BizException.class) 36 @ResponseStatus(code = HttpStatus.EXPECTATION_FAILED) 37 public Result<?> bizExceptionHandler(HttpServletRequest request, BizException bizException) { 38 int errorCode = bizException.getCode(); 39 log.error("catch bizException {}", errorCode); 40 return Result.builder().message(bizException.getMessage()).status(errorCode).build(); 41 } 42 43 44 /** 45 * 针对Validate校验异常统一处理 46 * @param request 47 * @param methodArgumentNotValidException 48 * @return 49 */ 50 @ExceptionHandler(MethodArgumentNotValidException.class) 51 @ResponseStatus(code = HttpStatus.BAD_REQUEST) 52 public Result<?> methodArgumentNotValidExceptionExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException methodArgumentNotValidException) { 53 Result result = new Result(); 54 log.error("catch methodArgumentNotValidException :" + methodArgumentNotValidException.getMessage(), methodArgumentNotValidException); 55 return ResultUtils.INSTANCE.getFailResult(methodArgumentNotValidException.getBindingResult(),result); 56 } 57 58 /** 59 * 针对Assert断言异常统一处理 60 * @param request 61 * @param illegalArgumentExceptionException 62 * @return 63 */ 64 @ExceptionHandler(IllegalArgumentException.class) 65 @ResponseStatus(code = HttpStatus.EXPECTATION_FAILED) 66 public Result<?> illegalArgumentExceptionHandler(HttpServletRequest request, IllegalArgumentException illegalArgumentExceptionException) { 67 log.error("illegalArgumentExceptionException:"+illegalArgumentExceptionException.getMessage(), illegalArgumentExceptionException); 68 return Result.builder().message(illegalArgumentExceptionException.getMessage()).status(Result.fail).build(); 69 } 70 71

测试验证

1、编写业务DTO

1@Data 2@AllArgsConstructor 3@NoArgsConstructor 4@Builder 5@ApiModel 6public class UserDTO implements Serializable { 7 8 @NotNull(message = "编号不能为空",groups = {Update.class, Delete.class}) 9 @ApiModelProperty(value = "编号",name = "id",example = "1") 10 private Long id; 11 12 @NotBlank(message = "用户名不能为空",groups = {Add.class}) 13 @ApiModelProperty(value = "用户名",name = "userName",example = "zhangsan") 14 private String userName; 15 16 @NotBlank(message = "姓名不能为空",groups = {Add.class}) 17 @ApiModelProperty(value = "姓名",name = "realName",example = "张三") 18 private String realName; 19 20 @NotBlank(message = "密码不能为空",groups = {Add.class}) 21 @Size(max=32,min=6,message = "密码长度要在6-32之间",groups = {Add.class}) 22 @ApiModelProperty(value = "密码",name = "password",example = "123456") 23 private String password; 24 25 @NotNull(message = "性别不能为空",groups = {Add.class}) 26 @ApiModelProperty(value = "性别",name = "gender",example = "1") 27 @EnumValid(target = Gender.class, message = "性别取值必须为0或者1",groups = {Add.class,Update.class}) 28 private Integer gender; 29 30 @ApiModelProperty(value = "邮箱",name = "email",example = "zhangsan@qq.com") 31 @Pattern(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$",message = "不满足邮箱正则表达式",groups = {Add.class,Update.class}) 32 private String email; 33 34 35 36}

2、编写业务controller

1@RestController 2@Api(tags = "用户管理") 3@Slf4j 4public class UserController { 5 6 @Autowired 7 private UserService userService; 8 9 @GetMapping(value="/get/{id}") 10 @ApiOperation("根据用户ID查找用户") 11 @ApiImplicitParam(value = "用户id",name = "id",required = true,paramType = "path") 12 public UserDTO getUserById(@PathVariable("id") Long id){ 13 UserDTO dto = userService.getUserById(id); 14 log.info("{}",dto); 15 return dto; 16 17 } 18 19 @PostMapping(value="/add") 20 @ApiOperation("添加用户") 21 public UserDTO add(@RequestBody @Validated({Add.class}) UserDTO userDTO){ 22 log.info("{}",userDTO); 23 return userService.save(userDTO); 24 } 25 26 @PostMapping(value="/update") 27 @ApiOperation("更新用户") 28 public UserDTO update(@RequestBody @Validated({Update.class}) UserDTO userDTO){ 29 log.info("{}",userDTO); 30 return userService.save(userDTO); 31 } 32 33 @DeleteMapping(value="/detele") 34 @ApiOperation("删除用户") 35 public boolean delete(@Validated({Delete.class}) UserDTO userDTO){ 36 log.info("id:{}",userDTO.getId()); 37 return userService.delete(userDTO.getId()); 38 } 39} 40

注: 业务service就不贴了和文章内容关系不大。如果感兴趣的朋友,可以从文末提供的链接进行查看

3、利用swagger在线接口文档进行测试

a:正常响应时,返回值形如下

1{ 2 "status": 0, 3 "message": "success", 4 "data": { 5 "id": 1, 6 "userName": "zhangsan", 7 "realName": "张三", 8 "password": "123456", 9 "gender": 1, 10 "email": "zhangsan@qq.com" 11 } 12}

b:当数据校验异常时,返回值形如下

1{ 2 "status": 1, 3 "message": "姓名不能为空;", 4 "data": null 5}

c:当业务异常时,返回值形如下

1{ 2 "status": 1, 3 "message": "user is not found by id :3", 4 "data": null 5}

总结

本文主要介绍了如何利用@RestControllerAdvice和ResponseBodyAdvice来统一处理返回值。本文代码示例还实现了分组校验,自定义校验,利用mdc traceId日志埋点,如果对这些内容感兴趣的朋友,可以查看文末项目链接

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-unit-resp

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )