SpringMVC系列第19篇:ResponseBodyAdvice:对@ResponseBody进行增强

star2017 1年前 ⋅ 348 阅读

大家好,我是路人,这是SpringMVC系列第19篇。

目录

1、前言

上一篇中介绍了RequestBodyAdvice接口,可以对@RequestBody进行增强,本文介绍另外一个相似的接口:ResponseBodyAdvice,这个可以对@ResponseBody进行增强,可以拦截@ResponseBody标注的方法的返回值,对返回值进行统一处理,比如进行加密、包装等操作;比如通过他可以实现统一的返回值。

2、接口如何实现统一返回值?

要求系统中所有返回json格式数据的接口都需要返回下面格式的数据。

  1. {
  2. "success": true,
  3. "code": null,
  4. "msg": "操作成功!",
  5. "data": 具体的数据
  6. }

但是咱们系统中所有的接口返回值都是下面这种格式的,难道咱们要一个个去手动改一下接口的返回值么?

SpringMVC为我们提供了更简单的方法,此时我们可以使用RequestBodyAdvice来实现,拦截所有@ResponseBody接口,将接口的返回值包装一下,包装为统一的格式返回,下面来看具体代码如何实现。

3、案例:通过RequestBodyAdvice实现统一返回值

3.1、git代码位置

  1. https://gitee.com/javacode2018/springmvc-series

3.2、定义返回值的通用类型

  1. package com.javacode2018.springmvc.chat14.dto;
  2. /**
  3. * rest接口通用返回值数据结构
  4. *
  5. * @param <T>
  6. */
  7. public class ResultDto<T> {
  8. //接口状态(成功还是失败)
  9. private Boolean success;
  10. //错误码
  11. private String code;
  12. //提示信息
  13. private String msg;
  14. //数据
  15. private T data;
  16. public Boolean getSuccess() {
  17. return success;
  18. }
  19. public void setSuccess(Boolean success) {
  20. this.success = success;
  21. }
  22. public String getCode() {
  23. return code;
  24. }
  25. public void setCode(String code) {
  26. this.code = code;
  27. }
  28. public String getMsg() {
  29. return msg;
  30. }
  31. public void setMsg(String msg) {
  32. this.msg = msg;
  33. }
  34. public T getData() {
  35. return data;
  36. }
  37. public void setData(T data) {
  38. this.data = data;
  39. }
  40. public static <T> ResultDto<T> success(T data) {
  41. return success(data, "操作成功!");
  42. }
  43. public static <T> ResultDto<T> success(T data, String msg) {
  44. ResultDto<T> result = new ResultDto<>();
  45. result.setSuccess(Boolean.TRUE);
  46. result.setMsg(msg);
  47. result.setData(data);
  48. return result;
  49. }
  50. public static <T> ResultDto<T> error(String msg) {
  51. return error(null, msg);
  52. }
  53. public static <T> ResultDto<T> error(String code, String msg) {
  54. return error(code, msg, null);
  55. }
  56. public static <T> ResultDto<T> error(String code, String msg, T data) {
  57. ResultDto<T> result = new ResultDto<>();
  58. result.setSuccess(Boolean.FALSE);
  59. result.setCode(code);
  60. result.setMsg(msg);
  61. result.setData(data);
  62. return result;
  63. }
  64. }

3.3、自定义一个ResponseBodyAdvice

  1. package com.javacode2018.springmvc.chat14.config;
  2. import com.javacode2018.springmvc.chat14.dto.ResultDto;
  3. import org.springframework.core.MethodParameter;
  4. import org.springframework.core.io.Resource;
  5. import org.springframework.http.MediaType;
  6. import org.springframework.http.converter.HttpMessageConverter;
  7. import org.springframework.http.server.ServerHttpRequest;
  8. import org.springframework.http.server.ServerHttpResponse;
  9. import org.springframework.web.bind.annotation.ControllerAdvice;
  10. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  11. import java.util.HashSet;
  12. import java.util.Set;
  13. @ControllerAdvice
  14. public class ResultDtoResponseBodyAdvice implements ResponseBodyAdvice<Object> {
  15. //不支持的类型列表
  16. private static final Set<Class<?>> NO_SUPPORTED_CLASSES = new HashSet<>(8);
  17. static {
  18. NO_SUPPORTED_CLASSES.add(ResultDto.class);
  19. NO_SUPPORTED_CLASSES.add(String.class);
  20. NO_SUPPORTED_CLASSES.add(byte[].class);
  21. NO_SUPPORTED_CLASSES.add(Resource.class);
  22. NO_SUPPORTED_CLASSES.add(javax.xml.transform.Source.class);
  23. }
  24. @Override
  25. public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  26. //如果返回值是NO_SUPPORTED_CLASSES中的类型,则不会被当前类的beforeBodyWrite方法处理,即不会被包装为ResultDto类型
  27. if (NO_SUPPORTED_CLASSES.contains(returnType.getParameterType())) {
  28. return false;
  29. } else {
  30. return true;
  31. }
  32. }
  33. @Override
  34. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  35. return ResultDto.success(body);
  36. }
  37. }
  • 需要实现ResponseBodyAdvice接口
  • 类上需要标注@ControllerAdvice注解
  • springmvc内部会对@ResponseBody方法的返回值进行处理,会先调用ResponseBodyAdvice的supports方法,如果方法返回true,则会进到其beforeBodyWrite方法中,我们在这个方法中将其包装为需求中统一的格式返回。
  • 大家需要注意supports方法,内部排除NO_SUPPORTED_CLASSES中指定的5种类型,这几种类型的返回值不会被处理。

3.4、来个controller测试效果

  1. @RestController
  2. public class UserController {
  3. @RequestMapping("/user")
  4. public User user() {
  5. return new User("路人", 30);
  6. }
  7. @RequestMapping("/user/list")
  8. public List<User> list() {
  9. List<User> result = Arrays.asList(new User("SpringMVC系列", 3), new User("SpringBoot系列", 2));
  10. return result;
  11. }
  12. @RequestMapping("/user/m1")
  13. public String m1() {
  14. return "ok";
  15. }
  16. @RequestMapping("/user/m2")
  17. public Integer m2() {
  18. return 1;
  19. }
  20. @RequestMapping("/user/m3")
  21. public ResultDto<String> m3() {
  22. return ResultDto.success("ok");
  23. }
  24. public static class User {
  25. private String name;
  26. private Integer age;
  27. public User() {
  28. }
  29. public User(String name, Integer age) {
  30. this.name = name;
  31. this.age = age;
  32. }
  33. public String getName() {
  34. return name;
  35. }
  36. public void setName(String name) {
  37. this.name = name;
  38. }
  39. public Integer getAge() {
  40. return age;
  41. }
  42. public void setAge(Integer age) {
  43. this.age = age;
  44. }
  45. @Override
  46. public String toString() {
  47. return "User{" +
  48. "name='" + name + '\'' +
  49. ", age=" + age +
  50. '}';
  51. }
  52. }
  53. }

controller中定义了5个接口,来看看他们的返回值,顺便看下他们是否会被ResultDtoResponseBodyAdvice处理为统一的格式呢?

方法/接口 返回值 是否会被ResultDtoResponseBodyAdvice处理?
/user User
/user/list List<User>
/user/m1 String
/user/m2 Integer
/user/m3 ResultDto

3.5、验证接口输出

/user接口: 输出如下,说明被统一处理了

  1. {
  2. "success": true,
  3. "code": null,
  4. "msg": "操作成功!",
  5. "data": {
  6. "name": "路人",
  7. "age": 30
  8. }
  9. }

/user/list接口: 输出如下,说明被统一处理了

  1. {
  2. "success": true,
  3. "code": null,
  4. "msg": "操作成功!",
  5. "data": [
  6. {
  7. "name": "SpringMVC系列",
  8. "age": 3
  9. },
  10. {
  11. "name": "SpringBoot系列",
  12. "age": 2
  13. }
  14. ]
  15. }

/user/m1接口: 输出如下,说明没有被统一处理,直接将controller中方法返回的值直接输出了

  1. ok

/user/m2接口: 输出如下,说明也被统一处理了

  1. {
  2. "success": true,
  3. "code": null,
  4. "msg": "操作成功!",
  5. "data": 1
  6. }

/user/m3接口: 直接返回的是ResultDto类型的,没有被统一处理

  1. {
  2. "success": true,
  3. "code": null,
  4. "msg": "操作成功!",
  5. "data": "ok"
  6. }

4、多个ResponseBodyAdvice指定顺序

当程序中定义了多个ResponseBodyAdvice,可以通过下面2种方式来指定顺序。

方式1:使用@org.springframework.core.annotation.Order注解指定顺序,顺序按照value的值从小到大

方式2:实现org.springframework.core.Ordered接口,顺序从小到大

5、@ControllerAdvice指定增强的范围

@ControllerAdvice注解相当于对Controller的功能进行了增强,目前来看,对所有的controller方法都增强了。

那么,能否控制一下增强的范围呢?比如对某些包中的controller进行增强,或者通过其他更细的条件来控制呢?

确实可以,可以通过@ControllerAdvice中的属性来指定增强的范围,需要满足这些条件的才会被@ControllerAdvice注解标注的bean增强,每个属性都是数组类型的,所有的条件是或者的关系,满足一个即可。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface ControllerAdvice {
  6. /**
  7. * 用来指定controller所在的包,满足一个就可以
  8. */
  9. @AliasFor("basePackages")
  10. String[] value() default {};
  11. /**
  12. * 用来指定controller所在的包,满足一个就可以
  13. */
  14. @AliasFor("value")
  15. String[] basePackages() default {};
  16. /**
  17. * controller所在的包必须为basePackageClasses中同等级或者子包中,满足一个就可以
  18. */
  19. Class<?>[] basePackageClasses() default {};
  20. /**
  21. * 用来指定Controller需要满足的类型,满足assignableTypes中指定的任意一个就可以
  22. */
  23. Class<?>[] assignableTypes() default {};
  24. /**
  25. * 用来指定Controller上需要有的注解,满足annotations中指定的任意一个就可以
  26. */
  27. Class<? extends Annotation>[] annotations() default {};
  28. }

扩展知识:这块的判断对应的源码如下,有兴趣的可以看看。

  1. org.springframework.web.method.HandlerTypePredicate#test

6、ResponseBodyAdvice原理

有些朋友可能对@ControllerAdvice和ResponseBodyAdvice的原理比较感兴趣,想研究一下他们的源码,关键代码在下面这个方法中,比较简单,有兴趣的可以去翻阅一下,这里就不展开说了。

  1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
  2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#initControllerAdviceCache

7、留个问题

当系统异常的时候,如何统一异常的输出呢?这里留给大家去思考一下,可以在留言中发表你的想法。

最新资料

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: