前言

在剖析完 「Spring Boot 统一数据格式是怎么实现的? 」文章之后,一直觉得有必要说明一下 Spring's Data Binding Mechanism 「Spring 数据绑定机制」。

默认情况下,Spring 只知道如何转换简单数据类型。比如我们提交的 int、String 或 boolean类型的请求数据,它会自动绑定到与之对应的 Java 类型。但在实际项目中,远远不够,因为我们可能需要绑定更复杂的对象类型。

我们需要了解 Spring 数据绑定机制,这样我们就可以更灵活的做全局配置或自定义配置,进而让我们的 RESTful API 更简洁,可读性也更好。本文依旧先通过示例代码说明实现,然后进行源码分析,带领大家了解这个机制是如何生效的,知其所以然, Let's go......

Spring 数据绑定

日期绑定

先来看下面一小段代码

@RestController
@RequestMapping("/bindings/")
@Slf4j
public class BindingController { @GetMapping("/{date}")
public void getSpecificDateInfo(@PathVariable LocalDateTime date) {
log.info(date.toString());
}
}

当我们用 Postman 请求这个 API

http://localhost:8080/rgyb/bindings/2019-12-10 12:00:00

如我们所料,抛出数据类型转换异常



因为 Spring 默认不支持将 String 类型的请求参数转换为 LocalDateTime 类型,所以我们需要自定义 converter 「转换器」完整整个转换过程

自定义转换器 StringToLocalDateTimeConverter,使其实现 org.springframework.core.convert.converter.Converter<S, T> 接口,在重写的 convert 方法中实现我们自定义的转换逻辑

public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String s) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINESE);
return LocalDateTime.parse(s, formatter);
}
}

将转换器注册到上下文中:

@Configuration
public class UnifiedReturnConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLocalDateTimeConverter());
}
}

重新访问上面链接,查看控制台,按照预期得到相应转换结果:

c.e.unifiedreturn.api.BindingController  : 2019-12-10T12:00

知道了这个,比如我们常用的枚举类型也可以应用这种方式做数据绑定

枚举类型绑定

同样的套路,自定义转换器

public class StringToEnumConverter implements Converter<String, Modes> {

	@Override
public Modes convert(String s) {
return Modes.valueOf(s);
}
}

将其添加至上下文,请小伙伴们自行尝试吧,知道了这个,我们再也不用在 RESTful API 内部做数据转换了,我们做到了全局控制,同时让整个 API 看起来更加清晰简洁

绑定对象

在某些情况下,我们希望将数据绑定到对象,这时我们可能马上联想起来使用 @RequestBody 注解,该注解通常用于获取 POST 请求体,并将其转换相应的数据对象

在实际业务场景中,除了请求体中的数据,我们同样需要请求头中的数据,比如 token ,token 中包含当前登陆用户的信息,每一次 RESTful 请求我们都需要从 header 中获取 token 数据处理实际业务,这种场景,上文提到的 Converter 以及 @RequestBody 显然不能满足我们的需求,此时我们就要换另一种解决方案 : HandlerMethodArgumentResolver

首先我们需要自定义一个注解 LoginUser (运行时生效,作用于参数上)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface LoginUser {
}

然后自定义 LoginUserArgumentResolver ,使其实现 HandlerMethodArgumentResolver 接口

public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
//判断参数是否有自定义注解 LoginUser 修饰
return methodParameter.hasParameterAnnotation(LoginUser.class);
} @Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest(); LoginUserVo loginUserVo = new LoginUserVo(); String token = request.getHeader("token");
if (Strings.isNotBlank(token)){
//通常这里需要编写 token 解析逻辑,并将其放到 LoginUserVo 对象中
//logic
} //在此为了快速简洁的做演示说明,省略掉解析 token 部分,直接从 header 指定 key 中获取数据
loginUserVo.setId(Long.valueOf(request.getHeader("userId")));
loginUserVo.setName(request.getHeader("userName"));
return loginUserVo;
}
}

依旧将自定义的 LoginUserArgumentResolver 添加到上下文中

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginUserArgumentResolver());
}

编写 API:

@GetMapping("/id")
public void getLoginUserInfo(@LoginUser LoginUserVo loginUserVo) {
log.info(loginUserVo.toString());
}

通过 Postman 请求,在 header 中设置好相应的 K-V,如下图

http://localhost:8080/rgyb/bindings/id

发送请求,查看控制台,得到预期结果

c.e.unifiedreturn.api.BindingController  : LoginUserVo(id=111111, name=rgyb)

相信到这里,你已经了解了基本的使用,接下来我们进行源码分析,透过现象看本质 (希望可以打开 IDE 跟着步骤查看)

Spring 数据绑定源码分析

首先我们需要了解我们自定义的 LoginUserArgumentResolver 是如何被加载到上下文中的,在你看过 HttpMessageConverter转换原理解析Springboot返回统一JSON数据格式是怎么实现的?后,你也许已经有了眉目,同加载 MessageConverter 如出一辙,在 RequestMappingHandlerAdapter 类中,同样有添加 ArgumentResolver 的方法,该方法会把系统内置的 resolver 和用户自定义的 resolver 都加载到上下文中,关键代码展示如下:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList();
resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false));
//其他内置 resolver resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
...
... if (this.getCustomArgumentResolvers() != null) {
resolvers.addAll(this.getCustomArgumentResolvers());
} ...
...
return resolvers;
}

HttpMessageConverter转换原理解析 文章中有一段调用栈跟踪,我再次粘贴在此处,并用红框做出标记,其实我们在分析 messageConverter 时已经悄悄的路过了我们本节要说的内容

我们进入相应的类中瞧一瞧:

到这里你应该猛的了解这背后的道理了吧

接下来,我们来验证我们天天用的 @RequestBody 注解是不是这个套路呢?

处理该注解的类是 RequestResponseBodyMethodProcessor,查看其类图,发现其依旧实现了 HandlerMethodArgumentResolver 接口

打开该类,你会看到下图代码,重点地方我已标记出来

整体处理流程如出一辙,只不过在里面调用了 messageConverter 来解析 JSON 数据。

总结

本文说的 Converter 和 ArgumentResolver 以及在 Spring MVC 中常用的 @InitBinder 注解整体过程都如出一辙,大家都可以按照这个思路来查看具体的实现。另外,在我们完成日常编码工作时,都可以从 Spring 现有的处理方式中摸索到一些解决方案,但前提是你了解 Spring 底层的一些调用过程

最后希望小伙伴打开 IDE 切实查看相应代码,你一定还会有新发现,我们可以一起探讨。本文代码已上传,公众号回复「demo」,打开链接查看 「spring-boot-unified-return」文件夹内容即可,也可以顺路回顾以前 Spring Boot 统一返回格式的代码实现


灵魂追问

  1. 如上图所示,在追中源码时,发现HandlerMethodArgumentResolverCompositeHandlerMethodArgumentResolver 的实现类之一,其中有一个 Map 类型的成员变量,通常我们使用 Map,key 的类型多数为 String 类型,但看到这个 Map 中有这样的 key 你马上想到的是什么?基础面试经常会问 equals 和 hashcode 的问题,下一篇文章会借着这个类来分析说明一下你总困惑的这件小事
  2. 对于 Spring Boot 的整个调用过程,你能描述出整体流程吗?
  3. Spring 内置多少个 Resolver?你可以跟踪调试获取到

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


如何妙用Spring 数据绑定机制?的更多相关文章

  1. Spring MVC—数据绑定机制,数据转换,数据格式化配置,数据校验

    Spring MVC数据绑定机制 数据转换 Spring MVC处理JSON 数据格式化配置使用 数据校验 数据校验 Spring MVC数据绑定机制 Spring MVC解析JSON格式的数据: 步 ...

  2. Spring 事务机制详解

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  3. spring工作机制

    Hibernate.struts,还差一个spring 就一起发出去.. spring工作机制及为什么要用? 1.springmvc请所有的请求都提交给DispatcherServlet,它会委托应用 ...

  4. 剖析WPF数据绑定机制

    引言 WPF框架采取的是MVVM模式,也就是数据驱动UI,UI控件(Controls)被严格地限制在表示层内,不会参与业务逻辑的处理,只是通过数据绑定(Data Binding)简单忠实地表达与之绑定 ...

  5. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

    这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...

  6. Hibernate工作原理及为什么要用?. Struts工作机制?为什么要使用Struts? spring工作机制及为什么要用?

    三大框架是用来开发web应用程序中使用的.Struts:基于MVC的充当了其中的试图层和控制器Hibernate:做持久化的,对JDBC轻量级的封装,使得我们能过面向对象的操作数据库Spring: 采 ...

  7. Spring 事务机制详解(事务的隔离性和传播性)

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  8. AngularJS 作用域与数据绑定机制

    AngularJS 简介 AngularJS 是由 Google 发起的一款开源的前端 MVC 脚本框架,既适合做普通 WEB 应用也可以做 SPA(单页面应用,所有的用户操作都在一个页面中完成).与 ...

  9. Spring事件机制详解

    一.前言 说来惭愧,对应Spring事件机制之前只知道实现 ApplicationListener 接口,就可以基于Spring自带的事件做一些事情(如ContextRefreshedEvent),但 ...

随机推荐

  1. 一、netcore跨平台之 Linux上部署netcore和webapi

    这几天闲着的时候在linux上部署了一下netcore webapi,下面就纪要一下这个过程. 中间遇到不少的坑,心里都是泪啊. 话不多说,开始干活. ------------------------ ...

  2. 🔥「课代表」帮你总结了全网最全的Redis知识点

    你知道的越多,你不知道的越多 点赞再看,养成习惯 GitHub上已经开源 https://github.com/JavaFamily 有一线大厂面试点脑图.个人联系方式和人才交流群,欢迎Star和指教 ...

  3. thinkphp6.0 开启调试模式以及Driver [Think] not supported

    thinkphp6.0 开启调试模式 首先确认自己是通过 composer 进行的下载,然后修改系统目录下的 .example.env 为 .env 文件 修改 config->app.php ...

  4. 【Leetcode 做题学算法周刊】第四期

    首发于微信公众号<前端成长记>,写于 2019.11.21 背景 本文记录刷题过程中的整个思考过程,以供参考.主要内容涵盖: 题目分析设想 编写代码验证 查阅他人解法 思考总结 目录 67 ...

  5. Linux入门之简介

    1.啥是linux? Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户.多任务.支持多线程和多CPU的操作系统. 它能运行主要的Unix工具软件.应用程序 ...

  6. 领扣(LeetCode)各位相加 个人题解

    给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数. 示例: 输入: 38 输出: 2 解释: 各位相加的过程为:3 + 8 = 11, 1 + 1 = 2. 由于 2 是一位数,所 ...

  7. ASP使用ajax来传递中文参数的编码处理

    背景 asp的第一版是0.9测试版,自从1996年ASP1.0诞生,迄今20余载.虽然asp在Windows2000 IIS服务5.0所附带的ASP 3.0发布后好像再没有更新过了,但是由于其入手简单 ...

  8. Spring Security框架下实现两周内自动登录"记住我"功能

    本文是Spring Security系列中的一篇.在上一篇文章中,我们通过实现UserDetailsService和UserDetails接口,实现了动态的从数据库加载用户.角色.权限相关信息,从而实 ...

  9. mysql8.0.13安装、使用教程图解

    mysql8.0.13安装.使用教程图解 MySQL是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Manageme ...

  10. LVM扩容之xfs文件系统

    LVM的基础请见:https://www.cnblogs.com/wxxjianchi/p/9698089.html 一.放大LV的容量.放大容量是由内而外来操作的. 1.设置新的lvm分区:用fdi ...