关于Spring中ResponseBodyAdvice的使用

  • 1 ResponseBodyAdvice的简介
  • 2 ResponseBodyAdvice的使用
        • 1 准备一个SpringBoot项目环境
        • 2 添加一个响应拦截类
        • 3 添加一个返回包装类
        • 4 添加控制类
        • 5 接口测试
        • 6 字符串转换问题解决

ResponseBodyAdvice可以在注解@ResponseBody将返回值处理成相应格式之前操作返回值。实现这个接口即可完成相应操作。可用于对response 数据的一些统一封装或者加密等操作

1 ResponseBodyAdvice的简介

ResponseBodyAdvice接口和之前记录的RequestBodyAdvice接口类似, RequestBodyAdvice是请求到Controller之前拦截,做相应的处理操作, 而ResponseBodyAdvice是对Controller返回的{@code @ResponseBody}or a {@code ResponseEntity} 后,{@code HttpMessageConverter} 类型转换之前拦截, 进行相应的处理操作后,再将结果返回给客户端.

ResponseBodyAdvice的源代码:

/**   数据的处理顺序向下  * Allows customizing the response after the execution of an {@code @ResponseBody}  * or a {@code ResponseEntity} controller method but before the body is written  * with an {@code HttpMessageConverter}.  *  * 

Implementations may be registered directly with * {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver} * or more likely annotated with {@code @ControllerAdvice} in which case they * will be auto-detected by both. * * @author Rossen Stoyanchev * @since 4.1 * @param the body type */ public interface ResponseBodyAdvice<T> { /** * Whether this component supports the given controller method return type * and the selected {@code HttpMessageConverter} type. * @param returnType the return type 方法返回的类型 * @param converterType the selected converter type 参数类型装换 * @return {@code true} if {@link #beforeBodyWrite} should be invoked; * {@code false} otherwise * 返回 true 则下面 beforeBodyWrite方法被调用, 否则就不调用下述方法 */ boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType); /** * Invoked after an {@code HttpMessageConverter} is selected and just before * its write method is invoked. * @param body the body to be written * @param returnType the return type of the controller method * @param selectedContentType the content type selected through content negotiation * @param selectedConverterType the converter type selected to write to the response * @param request the current request * @param response the current response * @return the body that was passed in or a modified (possibly new) instance */ @Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response); }

说明:

  • supports方法: 判断是否要执行beforeBodyWrite方法,true为执行,false不执行. 通过该方法可以选择哪些类或那些方法的response要进行处理, 其他的不进行处理.
  • beforeBodyWrite方法: 对response方法进行具体操作处理

{@code @ResponseBody} 返回响应体, 例如List集合

{@code ResponseEntity} 返回响应实体对象,例如User对象

2 ResponseBodyAdvice的使用

1 准备一个SpringBoot项目环境

2 添加一个响应拦截类

@ControllerAdvice public class BaseResponseBodyAdvice implements ResponseBodyAdvice<Object> {       @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType,             MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request,             ServerHttpResponse response) {          // 遇到feign接口之类的请求, 不应该再次包装,应该直接返回         // 上述问题的解决方案: 可以在feign拦截器中,给feign请求头中添加一个标识字段, 表示是feign请求         // 在此处拦截到feign标识字段, 则直接放行 返回body.          System.out.println("响应拦截成功");          if (body instanceof BaseResponse) {             return body;         } else if (body == null) {             return BaseResponse.ok();         } else {             return BaseResponse.ok(body);         }     } } 

3 添加一个返回包装类

@Data @AllArgsConstructor @NoArgsConstructor public class BaseResponse<T> {      private T data;     private int status = 200;     private String message;     private long srvTime = System.currentTimeMillis();      public BaseResponse(String message) {         this.message = message;     }      public BaseResponse<T> setData(T data) {         this.data = data;         return this;     }      public static <T> BaseResponse<T> ok() {         return new BaseResponse<>("操作成功");     }      public static <T> BaseResponse<T> ok(T data) {         return new BaseResponse<T>("操作成功").setData(data);     }  } 

4 添加控制类

@Controller @RequestMapping("/hello") public class HelloWorld {      // 此处数据从数据库中查询, 案例中也可以使用伪数据代替     @Autowired     private UserMapper userMapper;      // {@code ResponseEntity} 案列     @GetMapping("/one")     @ResponseBody     public User one() {          List<User> users = userMapper.selectAll();         System.out.println(users.get(0));         return users.get(0);     }           // {@code @ResponseBody}  案列     @GetMapping("/list")     @ResponseBody     public List<User> list() {          List<User> users = userMapper.selectAll();         System.out.println(users);         return users;     } }     

5 接口测试

浏览器访问: http://localhost:8080/hello/one

User(id=1, username=李子柒, phone=77777, icon=李子柒的头像, queryTime=Wed Oct 27 20:47:02 CST 2021) 响应拦截成功 

浏览器访问: http://localhost:8080/hello/list

[User(id=1, username=李子柒, phone=77777, icon=李子柒的头像, queryTime=Wed Oct 27 20:46:58 CST 2021)] 响应拦截成功 

ps: 如果直接响应字符串返回,则会报类型转换异常 cannot be cast to java.lang.String

6 字符串转换问题解决

根据debug发现,Sring类型的参数值org.springframework.http.converter.StringHttpMessageConverter, 其他类型参数值org.springframework.http.converter.json.MappingJackson2HttpMessageConverter, 根据上述可知,使用Mapping类型映射即可解决问题.

@ControllerAdvice public class BaseResponseBodyAdvice implements ResponseBodyAdvice<Object> {       @Override     public boolean supports(MethodParameter returnType, Class converterType) {         return true;     }      @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType,             MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request,             ServerHttpResponse response) {          // 遇到feign接口之类的请求, 不应该再次包装,应该直接返回         // 上述问题的解决方案: 可以在feign拦截器中,给feign请求头中添加一个标识字段, 表示是feign请求         // 在此处拦截到feign标识字段, 则直接放行 返回body.          System.out.println("响应拦截成功");                  // 如果返回值是String类型,那就手动把Result对象转换成JSON字符串         if(body instanceof String){         return this.objectMapper.writeValueAsString(Result.success(body));         }else if (body instanceof BaseResponse) {             return body;         } else if (body == null) {             return BaseResponse.ok();         } else {             return BaseResponse.ok(body);         }     } }