前言

最近项目组有个老我的项目要进行前后端拆散革新,应前端同学的要求,其后端提供的返回值格局需形如

{  "status": 0,  "message": "success",  "data": {      }}

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

@Data@AllArgsConstructor@NoArgsConstructor@Builderpublic 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")@Slf4jpublic 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 = "用户治理")@Slf4jpublic 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@Builderpublic 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")@Slf4jpublic 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@ApiModelpublic 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 = "用户治理")@Slf4jpublic 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...