共计 7170 个字符,预计需要花费 18 分钟才能阅读完成。
前言
最近项目组有个老我的项目要进行前后端拆散革新,应前端同学的要求,其后端提供的返回值格局需形如
{ | |
"status": 0, | |
"message": "success", | |
"data": {}} |
不便前端数据处理。要实现前端同学这个需要,其实也挺简略的,仅需做如下革新,新增一个返回对象,形如
@Data | |
@AllArgsConstructor | |
@NoArgsConstructor | |
@Builder | |
public class Result<T> { | |
public static final int success = 0; | |
public static final int fail = 1; | |
private int status = success; | |
private String message = "success"; | |
private T data; | |
} |
而后 controller 革新成如下
@RestController | |
@RequestMapping("/user") | |
@Slf4j | |
public class UserController { | |
@Autowired | |
private UserService userService; | |
@PostMapping(value="/add") | |
public Result<UserDTO> addUser(@Valid UserDTO userDTO, BindingResult bindingResult){Result<UserDTO> result = new Result<>(); | |
if (bindingResult.hasErrors()){return getUserFailResult(bindingResult, result); | |
} | |
saveUser(userDTO, result); | |
return result; | |
} | |
} |
仅仅须要这么革新就能够满足前端同学的述求。但这边存在一个问题就是,这个我的项目后端接口的 contoller 之前都是间接返回业务 bean 对象,形如下
@RestController | |
@Api(tags = "用户治理") | |
@Slf4j | |
public class UserController { | |
@Autowired | |
private UserService userService; | |
@GetMapping(value="/get/{id}") | |
@ApiOperation("依据用户 ID 查找用户") | |
@ApiImplicitParam(value = "用户 id",name = "id",required = true,paramType = "path") | |
public UserDTO getUserById(@PathVariable("id") Long id){UserDTO dto = userService.getUserById(id); | |
log.info("{}",dto); | |
return dto; | |
} | |
} |
如果按下面的思路
把 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、编写一个通用的响应实体
@Data | |
@AllArgsConstructor | |
@NoArgsConstructor | |
@Builder | |
public class Result<T> { | |
public static final int success = 0; | |
public static final int fail = 1; | |
private int status = success; | |
private String message = "success"; | |
private T data; | |
} |
3、编写一个类上加上 @RestControllerAdvice 并实现 ResponseBodyAdvice 接口。用来对立解决响应值
@RestControllerAdvice(basePackages = "com.github.lybgeek") | |
@Slf4j | |
public class ResponseAdvice implements ResponseBodyAdvice { | |
@Override | |
public boolean supports(MethodParameter methodParameter, Class aClass) {return true;} | |
@Override | |
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {if(Objects.isNull(o)){return Result.builder().message("success").build();} | |
if(o instanceof Result){return o;} | |
return Result.builder().message("success").data(o).build();} | |
@ExceptionHandler(Exception.class) | |
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) | |
public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {log.error(e.getMessage(), e); | |
return Result.builder().message(e.getMessage()).status(Result.fail).build();} | |
/** | |
* 针对业务异样对立解决 | |
* @param request | |
* @param bizException | |
* @return | |
*/ | |
@ExceptionHandler(BizException.class) | |
@ResponseStatus(code = HttpStatus.EXPECTATION_FAILED) | |
public Result<?> bizExceptionHandler(HttpServletRequest request, BizException bizException) {int errorCode = bizException.getCode(); | |
log.error("catch bizException {}", errorCode); | |
return Result.builder().message(bizException.getMessage()).status(errorCode).build();} | |
/** | |
* 针对 Validate 校验异样对立解决 | |
* @param request | |
* @param methodArgumentNotValidException | |
* @return | |
*/ | |
@ExceptionHandler(MethodArgumentNotValidException.class) | |
@ResponseStatus(code = HttpStatus.BAD_REQUEST) | |
public Result<?> methodArgumentNotValidExceptionExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException methodArgumentNotValidException) {Result result = new Result(); | |
log.error("catch methodArgumentNotValidException :" + methodArgumentNotValidException.getMessage(), methodArgumentNotValidException); | |
return ResultUtils.INSTANCE.getFailResult(methodArgumentNotValidException.getBindingResult(),result); | |
} | |
/** | |
* 针对 Assert 断言异样对立解决 | |
* @param request | |
* @param illegalArgumentExceptionException | |
* @return | |
*/ | |
@ExceptionHandler(IllegalArgumentException.class) | |
@ResponseStatus(code = HttpStatus.EXPECTATION_FAILED) | |
public Result<?> illegalArgumentExceptionHandler(HttpServletRequest request, IllegalArgumentException illegalArgumentExceptionException) {log.error("illegalArgumentExceptionException:"+illegalArgumentExceptionException.getMessage(), illegalArgumentExceptionException); | |
return Result.builder().message(illegalArgumentExceptionException.getMessage()).status(Result.fail).build();} | |
测试验证
1、编写业务 DTO
@Data | |
@AllArgsConstructor | |
@NoArgsConstructor | |
@Builder | |
@ApiModel | |
public class UserDTO implements Serializable {@NotNull(message = "编号不能为空",groups = {Update.class, Delete.class}) | |
@ApiModelProperty(value = "编号",name = "id",example = "1") | |
private Long id; | |
@NotBlank(message = "用户名不能为空",groups = {Add.class}) | |
@ApiModelProperty(value = "用户名",name = "userName",example = "zhangsan") | |
private String userName; | |
@NotBlank(message = "姓名不能为空",groups = {Add.class}) | |
@ApiModelProperty(value = "姓名",name = "realName",example = "张三") | |
private String realName; | |
@NotBlank(message = "明码不能为空",groups = {Add.class}) | |
@Size(max=32,min=6,message = "明码长度要在 6 -32 之间",groups = {Add.class}) | |
@ApiModelProperty(value = "明码",name = "password",example = "123456") | |
private String password; | |
@NotNull(message = "性别不能为空",groups = {Add.class}) | |
@ApiModelProperty(value = "性别",name = "gender",example = "1") | |
@EnumValid(target = Gender.class, message = "性别取值必须为 0 或者 1",groups = {Add.class,Update.class}) | |
private Integer gender; | |
@ApiModelProperty(value = "邮箱",name = "email",example = "zhangsan@qq.com") | |
@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}) | |
private String email; | |
} |
2、编写业务 controller
@RestController | |
@Api(tags = "用户治理") | |
@Slf4j | |
public class UserController { | |
@Autowired | |
private UserService userService; | |
@GetMapping(value="/get/{id}") | |
@ApiOperation("依据用户 ID 查找用户") | |
@ApiImplicitParam(value = "用户 id",name = "id",required = true,paramType = "path") | |
public UserDTO getUserById(@PathVariable("id") Long id){UserDTO dto = userService.getUserById(id); | |
log.info("{}",dto); | |
return dto; | |
} | |
@PostMapping(value="/add") | |
@ApiOperation("增加用户") | |
public UserDTO add(@RequestBody @Validated({Add.class}) UserDTO userDTO){log.info("{}",userDTO); | |
return userService.save(userDTO); | |
} | |
@PostMapping(value="/update") | |
@ApiOperation("更新用户") | |
public UserDTO update(@RequestBody @Validated({Update.class}) UserDTO userDTO){log.info("{}",userDTO); | |
return userService.save(userDTO); | |
} | |
@DeleteMapping(value="/detele") | |
@ApiOperation("删除用户") | |
public boolean delete(@Validated({Delete.class}) UserDTO userDTO){log.info("id:{}",userDTO.getId()); | |
return userService.delete(userDTO.getId()); | |
} | |
} |
注: 业务 service 就不贴了和文章内容关系不大。如果感兴趣的敌人,能够从文末提供的链接进行查看
3、利用 swagger 在线接口文档进行测试
a:失常响应时,返回值形如下
{ | |
"status": 0, | |
"message": "success", | |
"data": { | |
"id": 1, | |
"userName": "zhangsan", | |
"realName": "张三", | |
"password": "123456", | |
"gender": 1, | |
"email": "zhangsan@qq.com" | |
} | |
} |
b:当数据校验异样时,返回值形如下
{ | |
"status": 1, | |
"message": "姓名不能为空;", | |
"data": null | |
} |
c: 当业务异样时,返回值形如下
{ | |
"status": 1, | |
"message": "user is not found by id :3", | |
"data": null | |
} |
总结
本文次要介绍了如何利用 @RestControllerAdvice 和 ResponseBodyAdvice 来对立解决返回值。本文代码示例还实现了分组校验,自定义校验,利用 mdc traceId 日志埋点,如果对这些内容感兴趣的敌人,能够查看文末我的项目链接
demo 链接
https://github.com/lyb-geek/s…