User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   正文
Spring

SpringBoot—统一API接口响应格式

2022-09-23 15:33:20
809  0 1
参考目录 隐藏
1) HandlerMethodReturnValueHandler
2) ModelAndViewContainer
3) 案例

阅读完需:约 10 分钟

实现统一 API 接口响应格式的方式有很多的,比如之前的ResponseBodyAdvice 和 RequestBodyAdvice,比如拦截器等,但是这里使用的不是这些而是从返回处理器中去自定义,HandlerMethodReturnValueHandler,关于这个在之前的文章中有讲到的

SpringBoot—自定义参数解析器

SpringMVC—工作机制和请求生命周期

HandlerMethodReturnValueHandler

SpringBoot依赖版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

HandlerMethodReturnValueHandler 的作用是对处理器的处理结果再进行一次二次加工,这个接口里边有两个方法:

/**
 * Strategy interface to handle the value returned from the invocation of a
 * handler method .
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see HandlerMethodArgumentResolver
 */
public interface HandlerMethodReturnValueHandler {

	/**
	 * Whether the given {@linkplain MethodParameter method return type} is
	 * supported by this handler.
	 * @param returnType the method return type to check
	 * @return {@code true} if this handler supports the supplied return type;
	 * {@code false} otherwise
	 */
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * Handle the given return value by adding attributes to the model and
	 * setting a view or setting the
	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
	 * to indicate the response has been handled directly.
	 * @param returnValue the value returned from the handler method
	 * @param returnType the type of the return value. This type must have
	 * previously been passed to {@link #supportsReturnType} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @throws Exception if the return value handling results in an error
	 */
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
  • supportsReturnType:这个处理器是否支持相应的返回值类型。
  • handleReturnValue:对方法返回值进行处理。

HandlerMethodReturnValueHandler 有很多默认的实现类,我们来看下:

ViewNameMethodReturnValueHandler

这个处理器用来处理返回值为 void 和 String 的情况。如果返回值为 void,则不做任何处理。如果返回值为 String,则将 String 设置给 mavContainer 的 viewName 属性,同时判断这个 String 是不是重定向的 String,如果是,则设置 mavContainer 的 redirectModelScenario 属性为 true,这是处理器返回重定向视图的标志。

ViewMethodReturnValueHandler

这个处理器用来处理返回值为 View 的情况。如果返回值为 View,则将 View 设置给 mavContainer 的 view 属性,同时判断这个 View 是不是重定向的 View,如果是,则设置 mavContainer 的 redirectModelScenario 属性为 true,这是处理器返回重定向视图的标志。

MapMethodProcessor

这个处理器用来处理返回值类型为 Map 的情况,具体的处理方案就是将 map 添加到 mavContainer 的 model 属性中。

StreamingResponseBodyReturnValueHandler

这个用来处理 StreamingResponseBody 或者 ResponseEntity<StreamingResponseBody> 类型的返回值。

DeferredResultMethodReturnValueHandler

这个用来处理 DeferredResult、ListenableFuture 以及 CompletionStage 类型的返回值,用于异步请求。

CallableMethodReturnValueHandler

处理 Callable 类型的返回值,也是用于异步请求。

HttpHeadersReturnValueHandler

这个用来处理 HttpHeaders 类型的返回值,具体处理方式就是将 mavContainer 中的 requestHandled 属性设置为 true,该属性是请求是否已经处理完成的标志(如果处理完了,就到此为止,后面不会再去找视图了),然后将 HttpHeaders 添加到响应头中。

ModelMethodProcessor

这个用来处理返回值类型为 Model 的情况,具体的处理方式就是将 Model 添加到 mavContainer 的 model 上。

ModelAttributeMethodProcessor

这个用来处理添加了 @ModelAttribute 注解的返回值类型,如果 annotaionNotRequired 属性为 true,也可以用来处理其他非通用类型的返回值。

ServletModelAttributeMethodProcessor

同上,该类只是修改了参数解析方式。

ResponseBodyEmitterReturnValueHandler

这个用来处理返回值类型为 ResponseBodyEmitter 的情况。

ModelAndViewMethodReturnValueHandler

这个用来处理返回值类型为 ModelAndView 的情况,将返回值中的 Model 和 View 分别设置到 mavContainer 的相应属性上去。

ModelAndViewResolverMethodReturnValueHandler

这个的 supportsReturnType 方法返回 true,即可以处理所有类型的返回值,这个一般放在最后兜底。

AbstractMessageConverterMethodProcessor

这是一个抽象类,当返回值需要通过 HttpMessageConverter 进行转化的时候会用到它的子类。这个抽象类主要是定义了一些工具方法。

RequestResponseBodyMethodProcessor

这个用来处理添加了 @ResponseBody 注解的返回值类型。

HttpEntityMethodProcessor

这个用来处理返回值类型是 HttpEntity 并且不是 RequestEntity 的情况。

AsyncHandlerMethodReturnValueHandler

这是一个空接口,暂未发现典型使用场景。

AsyncTaskMethodReturnValueHandler

这个用来处理返回值类型为 WebAsyncTask 的情况。

HandlerMethodReturnValueHandlerComposite

看 Composite 就知道,这是一个组合处理器,没啥好说的。

这个就是系统默认定义的 HandlerMethodReturnValueHandler。

那么在上面的介绍中,大家看到反复涉及到一个组件 mavContainer,这个我也要和大家介绍一下。

ModelAndViewContainer

ModelAndViewContainer 就是一个数据穿梭巴士,在整个请求的过程中承担着数据传送的工作,从它的名字上我们可以看出来它里边保存着 Model 和 View 两种类型的数据,但是实际上可不止两种,我们来看下 ModelAndViewContainer 的定义:

public class ModelAndViewContainer {
 private boolean ignoreDefaultModelOnRedirect = false;
 @Nullable
 private Object view;
 private final ModelMap defaultModel = new BindingAwareModelMap();
 @Nullable
 private ModelMap redirectModel;
 private boolean redirectModelScenario = false;
 @Nullable
 private HttpStatus status;
 private final Set<String> noBinding = new HashSet<>(4);
 private final Set<String> bindingDisabled = new HashSet<>(4);
 private final SessionStatus sessionStatus = new SimpleSessionStatus();
 private boolean requestHandled = false;
}

把这几个属性理解了,基本上也就整明白 ModelAndViewContainer 的作用了:

  • defaultModel:默认使用的 Model。当我们在接口参数重使用 Model、ModelMap 或者 Map 时,最终使用的实现类都是 BindingAwareModelMap,对应的也都是 defaultModel。
  • redirectModel:重定向时候的 Model,如果我们在接口参数中使用了 RedirectAttributes 类型的参数,那么最终会传入 redirectModel。

可以看到,一共有两个 Model,两个 Model 到底用哪个呢?这个在 getModel 方法中根据条件返回合适的 Model:

public ModelMap getModel() {
 if (useDefaultModel()) {
  return this.defaultModel;
 }
 else {
  if (this.redirectModel == null) {
   this.redirectModel = new ModelMap();
  }
  return this.redirectModel;
 }
}
private boolean useDefaultModel() {
 return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}

这里 redirectModelScenario 表示处理器是否返回 redirect 视图;ignoreDefaultModelOnRedirect 表示是否在重定向时忽略 defaultModel,所以这块的逻辑是这样:

  1. 如果 redirectModelScenario 为 true,即处理器返回的是一个重定向视图,那么使用 redirectModel。如果 redirectModelScenario 为 false,即处理器返回的不是一个重定向视图,那么使用 defaultModel。
  2. 如果 redirectModel 为 null,并且 ignoreDefaultModelOnRedirect 为 false,则使用 redirectModel,否则使用 defaultModel。

接下来还剩下如下一些参数:

  • view:返回的视图。
  • status:HTTP 状态码。
  • noBinding:是否对 @ModelAttribute(binding=true/false) 声明的数据模型的相应属性进行绑定。
  • bindingDisabled:不需要进行数据绑定的属性。
  • sessionStatus:SessionAttribute 使用完成的标识。
  • requestHandled:请求处理完成的标识(例如添加了 @ResponseBody 注解的接口,这个属性为 true,请求就不会再去找视图了)。

接下来我们也来自定义一个 HandlerMethodReturnValueHandler,来感受一下 HandlerMethodReturnValueHandler 的基本用法。


案例

定义一个接口返回数据

@RestController
public class UserController {

    @GetMapping("/user/test")
    public User getUserByUsername(String username) {
        User user = new User();
        user.setName(username);
        return user;
    }
}

返回的数据结果

{
  "id": null,
  "name": "123",
  "createTime": null
}

但是我需要这样的结果

{
  "data": {
    "id": null,
    "name": "123",
    "createTime": null
  },
  "status": "ok"
}

就这样一个简单需求,看下怎么实现

首先要看一下这个类,RequestResponseBodyMethodProcessor这是 HandlerMethodReturnValueHandler 的实现类之一,这个主要用来处理返回 JSON 的情况。

@Override
public boolean supportsReturnType(MethodParameter returnType) {
 return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
   returnType.hasMethodAnnotation(ResponseBody.class));
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 mavContainer.setRequestHandled(true);
 ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
  • supportsReturnType:从这个方法中可以看到,这里支持有 @ResponseBody 注解的接口。
  • handleReturnValue:这是具体的处理逻辑,首先 mavContainer 中设置 requestHandled 属性为 true,表示这里处理完成后就完了,以后不用再去找视图了,然后分别获取 inputMessage 和 outputMessage,调用 writeWithMessageConverters 方法进行输出,writeWithMessageConverters 方法是在父类中定义的方法,这个方法比较长,核心逻辑就是调用确定输出数据、确定 MediaType,然后通过 HttpMessageConverter 将 JSON 数据写出去即可。

接下来我们就可以自己实现了

首先自定义一个 HandlerMethodReturnValueHandler:

public class MyHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    private HandlerMethodReturnValueHandler handler;

    public MyHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler handler) {
        this.handler = handler;
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return handler.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("status", "ok");
        map.put("data", returnValue);
        handler.handleReturnValue(map, returnType, mavContainer, webRequest);
    }
}

由于我们要做的功能其实是在 RequestResponseBodyMethodProcessor 基础之上实现的,因为支持 @ResponseBody,输出 JSON 那些东西都不变,我们只是在输出之前修改一下数据而已。所以我这里直接定义了一个属性 HandlerMethodReturnValueHandler,这个属性的实例就是 RequestResponseBodyMethodProcessor,supportsReturnType 方法就按照 RequestResponseBodyMethodProcessor 的要求来,在 handleReturnValue 方法中,我们先对返回值进行一个预处理,然后调用 RequestResponseBodyMethodProcessor#handleReturnValue 方法继续输出 JSON 即可。

接下来就是配置 MyHandlerMethodReturnValueHandler 使之生效了。由于 SpringMVC 中 HandlerAdapter 在加载的时候已经配置了 HandlerMethodReturnValueHandler,所以我们可以通过如下方式对已经配置好的 RequestMappingHandlerAdapter 进行修改,如下:

@Configuration
public class ReturnValueConfig implements InitializingBean {
    @Autowired
    RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    @Override
    public void afterPropertiesSet() throws Exception {
        List<HandlerMethodReturnValueHandler> originHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>(originHandlers.size());
        for (HandlerMethodReturnValueHandler originHandler : originHandlers) {
            if (originHandler instanceof RequestResponseBodyMethodProcessor) {
                newHandlers.add(new MyHandlerMethodReturnValueHandler(originHandler));
            }else{
                newHandlers.add(originHandler);
            }
        }
        requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
    }
}

自定义 ReturnValueConfig 实现 InitializingBean 接口,afterPropertiesSet 方法会被自动调用,在该方法中,我们将 RequestMappingHandlerAdapter 中已经配置好的 HandlerMethodReturnValueHandler 拎出来挨个检查,如果类型是 RequestResponseBodyMethodProcessor,则重新构建,用我们自定义的 MyHandlerMethodReturnValueHandler 代替它,最后给 requestMappingHandlerAdapter 重新设置 HandlerMethodReturnValueHandler 即可。

最后再提供一个测试接口:

@RestController
public class UserController {

    @GetMapping("/user/test")
    public User getUserByUsername(String username) {
        User user = new User();
        user.setName(username);
        return user;
    }
}

配置完成后,就可以启动项目啦。

对于InitializingBean在之前也讲过

Spring—IOC容器(构建)

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594476浏览

随机文章
Spring Boot—整合 Thymeleaf 页面模板
5年前
SpringMVC—RESTful风格
5年前
Kotlin-表达式—Lambda表达式(十四)
4年前
SpringMVC笔记7—Controller 方法的返回值
5年前
SpringSecurity—UsernamePasswordAuthenticationFilter 源码分析
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594476 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付