Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
SpringMVC教程
-> ResponseBodyAdvice:对@ResponseBody进行增强
1、Helloword
2、@Controller、@RequestMapping
3、接口测试利器
4、如何接受请求中的参数?
5、@RequestBody接收Json格式数据
6、多文件上传
7、返回页面常见的5种方式
8、返回json格式数据 & 通用返回值设计
9、SpringMVC返回null是什么意思?
10、异步处理请求
11、如何集成静态资源?
12、拦截器怎么用?
13、统一异常处理
14、实战篇:通用返回值 & 异常处理设计
15、全注解的方式 & 原理解析
16、源码解析SpringMVC处理请求的流程
17、源码解析SpringMVC容器的启动过程
18、RequestBodyAdvice:对@ReuqestBody进行增强
19、ResponseBodyAdvice:对@ResponseBody进行增强
20、RESTful接口详解
21、接口调用利器RestTemplate
22、参数解析器HandlerMethodArgumentResolver解密
23、@RequestParam用法及原理详解
24、@RequestBody原理解密
25、@RequestHeader详解
26、@CookieValue详解
27、@RequestAttribute详解
28、@SessionAttribute详解
29、重定向和转向详解
30、Converter转换器详解
31、跨域问题详解
32、类容协商,颠覆你的认知
33、终章
34、CORS通信
35、浏览器安全策略 & CORS
36、Http中的Content-Type详解
上一篇:RequestBodyAdvice:对@ReuqestBody进行增强
下一篇:RESTful接口详解
<div style="display:none"></div> 大家好,我是路人,这是SpringMVC系列第19篇。 ## 目录 [TOC] ## 1、前言 上一篇中介绍了RequestBodyAdvice接口,可以对@RequestBody进行增强,本文介绍另外一个相似的接口:`ResponseBodyAdvice`,这个可以对@ResponseBody进行增强,可以拦截@ResponseBody标注的方法的返回值,对返回值进行统一处理,比如进行加密、包装等操作;比如通过他可以实现统一的返回值。 ## 2、接口如何实现统一返回值? 要求系统中所有返回json格式数据的接口都需要返回下面格式的数据。 ```json { "success": true, "code": null, "msg": "操作成功!", "data": 具体的数据 } ``` 但是咱们系统中所有的接口返回值都是下面这种格式的,难道咱们要一个个去手动改一下接口的返回值么? ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/230/34864be1-f678-4c15-8ce1-f969bafb410e.png) SpringMVC为我们提供了更简单的方法,此时我们可以使用RequestBodyAdvice来实现,拦截所有@ResponseBody接口,将接口的返回值包装一下,包装为统一的格式返回,下面来看具体代码如何实现。 ## 3、案例:通过RequestBodyAdvice实现统一返回值 ### 3.1、git代码位置 ```http https://gitee.com/javacode2018/springmvc-series ``` ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/230/0cd1ab2e-41c2-414c-8aad-0c4ed734e005.png) ### 3.2、定义返回值的通用类型 ```java package com.javacode2018.springmvc.chat14.dto; /** * rest接口通用返回值数据结构 * * @param <T> */ public class ResultDto<T> { //接口状态(成功还是失败) private Boolean success; //错误码 private String code; //提示信息 private String msg; //数据 private T data; public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static <T> ResultDto<T> success(T data) { return success(data, "操作成功!"); } public static <T> ResultDto<T> success(T data, String msg) { ResultDto<T> result = new ResultDto<>(); result.setSuccess(Boolean.TRUE); result.setMsg(msg); result.setData(data); return result; } public static <T> ResultDto<T> error(String msg) { return error(null, msg); } public static <T> ResultDto<T> error(String code, String msg) { return error(code, msg, null); } public static <T> ResultDto<T> error(String code, String msg, T data) { ResultDto<T> result = new ResultDto<>(); result.setSuccess(Boolean.FALSE); result.setCode(code); result.setMsg(msg); result.setData(data); return result; } } ``` ### 3.3、自定义一个ResponseBodyAdvice ```java package com.javacode2018.springmvc.chat14.config; import com.javacode2018.springmvc.chat14.dto.ResultDto; import org.springframework.core.MethodParameter; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.HashSet; import java.util.Set; @ControllerAdvice public class ResultDtoResponseBodyAdvice implements ResponseBodyAdvice<Object> { //不支持的类型列表 private static final Set<Class<?>> NO_SUPPORTED_CLASSES = new HashSet<>(8); static { NO_SUPPORTED_CLASSES.add(ResultDto.class); NO_SUPPORTED_CLASSES.add(String.class); NO_SUPPORTED_CLASSES.add(byte[].class); NO_SUPPORTED_CLASSES.add(Resource.class); NO_SUPPORTED_CLASSES.add(javax.xml.transform.Source.class); } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { //如果返回值是NO_SUPPORTED_CLASSES中的类型,则不会被当前类的beforeBodyWrite方法处理,即不会被包装为ResultDto类型 if (NO_SUPPORTED_CLASSES.contains(returnType.getParameterType())) { return false; } else { return true; } } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return ResultDto.success(body); } } ``` > - 需要实现ResponseBodyAdvice接口 > - 类上需要标注@ControllerAdvice注解 > - springmvc内部会对@ResponseBody方法的返回值进行处理,会先调用ResponseBodyAdvice的supports方法,如果方法返回true,则会进到其`beforeBodyWrite`方法中,我们在这个方法中将其包装为需求中统一的格式返回。 > - 大家需要注意supports方法,内部排除NO_SUPPORTED_CLASSES中指定的5种类型,这几种类型的返回值不会被处理。 ### 3.4、来个controller测试效果 ```java @RestController public class UserController { @RequestMapping("/user") public User user() { return new User("路人", 30); } @RequestMapping("/user/list") public List<User> list() { List<User> result = Arrays.asList(new User("SpringMVC系列", 3), new User("SpringBoot系列", 2)); return result; } @RequestMapping("/user/m1") public String m1() { return "ok"; } @RequestMapping("/user/m2") public Integer m2() { return 1; } @RequestMapping("/user/m3") public ResultDto<String> m3() { return ResultDto.success("ok"); } public static class User { private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } } ``` controller中定义了5个接口,来看看他们的返回值,顺便看下他们是否会被ResultDtoResponseBodyAdvice处理为统一的格式呢? | 方法/接口 | 返回值 | 是否会被ResultDtoResponseBodyAdvice处理? | | ---------- | ------------- | ----------------------------------------- | | /user | User | 是 | | /user/list | List<User> | 是 | | /user/m1 | String | 否 | | /user/m2 | Integer | 是 | | /user/m3 | ResultDto | 否 | ### 3.5、验证接口输出 ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/230/82c6f60e-aae8-4a4a-aaa7-896e02f70d44.png) **/user接口:** 输出如下,说明被统一处理了 ```json { "success": true, "code": null, "msg": "操作成功!", "data": { "name": "路人", "age": 30 } } ``` **/user/list接口:** 输出如下,说明被统一处理了 ```json { "success": true, "code": null, "msg": "操作成功!", "data": [ { "name": "SpringMVC系列", "age": 3 }, { "name": "SpringBoot系列", "age": 2 } ] } ``` **/user/m1接口:** 输出如下,说明没有被统一处理,直接将controller中方法返回的值直接输出了 ``` ok ``` **/user/m2接口:** 输出如下,说明也被统一处理了 ```json { "success": true, "code": null, "msg": "操作成功!", "data": 1 } ``` **/user/m3接口:** 直接返回的是ResultDto类型的,没有被统一处理 ```json { "success": true, "code": null, "msg": "操作成功!", "data": "ok" } ``` ## 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增强,每个属性都是数组类型的,所有的条件是或者的关系,满足一个即可。 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface ControllerAdvice { /** * 用来指定controller所在的包,满足一个就可以 */ @AliasFor("basePackages") String[] value() default {}; /** * 用来指定controller所在的包,满足一个就可以 */ @AliasFor("value") String[] basePackages() default {}; /** * controller所在的包必须为basePackageClasses中同等级或者子包中,满足一个就可以 */ Class<?>[] basePackageClasses() default {}; /** * 用来指定Controller需要满足的类型,满足assignableTypes中指定的任意一个就可以 */ Class<?>[] assignableTypes() default {}; /** * 用来指定Controller上需要有的注解,满足annotations中指定的任意一个就可以 */ Class<? extends Annotation>[] annotations() default {}; } ``` **扩展知识**:这块的判断对应的源码如下,有兴趣的可以看看。 ``` org.springframework.web.method.HandlerTypePredicate#test ``` ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/230/6b4dbfac-5d59-4d16-8d22-a07e9ece7649.png) ## 6、ResponseBodyAdvice原理 有些朋友可能对`@ControllerAdvice和ResponseBodyAdvice`的原理比较感兴趣,想研究一下他们的源码,关键代码在下面这个方法中,比较简单,有兴趣的可以去翻阅一下,这里就不展开说了。 ```java org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#initControllerAdviceCache ``` ## 7、留个问题 当系统异常的时候,如何统一异常的输出呢?这里留给大家去思考一下,可以在留言中发表你的想法。 <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container