如何妙用Spring 数据绑定机制

如何妙用Spring 数据绑定机制

前言
在剖析完 Spring Boot 返回统一数据格式是怎样实现的?文章以后,一直以为有必要说明一下 Spring's Data Binding Mechanism 「Spring 数据绑定机制」。
默认状况下,Spring 只知道如何转换简单数据类型。好比咱们提交的 int、String 或 boolean类型的请求数据,它会自动绑定到与之对应的 Java 类型。但在实际项目中,远远不够,由于咱们可能须要绑定更复杂的对象类型。
咱们须要了解 Spring 数据绑定机制,这样咱们就能够更灵活的作全局配置或自定义配置,进而让咱们的 RESTful API 更简洁,可读性也更好。本文依旧先经过示例代码说明实现,而后进行源码分析,带领你们了解这个机制是如何生效的,知其因此然, Let's go......
Spring 数据绑定
日期绑定web

先来看下面一小段代码面试

@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 数据绑定机制spring

由于 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);
    }
}

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

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

从新访问上面连接,查看控制台,按照预期获得相应转换结果:微信

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

知道了这个,好比咱们经常使用的枚举类型也能够应用这种方式作数据绑定
枚举类型绑定ide

一样的套路,自定义转换器spring-boot

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 时已经悄悄的路过了咱们本节要说的内容
如何妙用Spring 数据绑定机制

咱们进入相应的类中瞧一瞧:
如何妙用Spring 数据绑定机制
到这里你应该猛的了解这背后的道理了吧
接下来,咱们来验证咱们每天用的 @RequestBody 注解是否是这个套路呢?处理该注解的类是 RequestResponseBodyMethodProcessor,查看其类图,发现其依旧实现了 HandlerMethodArgumentResolver 接口
如何妙用Spring 数据绑定机制

打开该类,你会看到下图代码,重点地方我已标记出来
如何妙用Spring 数据绑定机制

总体处理流程一模一样,只不过在里面调用了 messageConverter 来解析 JSON 数据。
总结
本文说的 Converter 和 ArgumentResolver 以及在 Spring MVC 中经常使用的 @InitBinder 注解总体过程都一模一样,你们均可以按照这个思路来查看具体的实现。另外,在咱们完成平常编码工做时,均可以从 Spring 现有的处理方式中摸索到一些解决方案,但前提是你了解 Spring 底层的一些调用过程
最后但愿小伙伴打开 IDE 切实查看相应代码,你必定还会有新发现,咱们能够一块儿探讨。本文代码已上传,公众号回复「demo」,打开连接查看 「spring-boot-unified-return」文件夹内容便可,也能够顺路回顾之前 Spring Boot 统一返回格式的代码实现
为了更好的回答小伙伴们的问题,同时与你们更好的交流学习,在公众号菜单上添加了个人我的微信号二维码,有须要的小伙伴们能够加我微信
灵魂追问
如何妙用Spring 数据绑定机制

  1. 如上图所示,在追中源码时,发现HandlerMethodArgumentResolverComposite 是 HandlerMethodArgumentResolver 的实现类之一,其中有一个 Map 类型的成员变量,一般咱们使用 Map,key 的类型多数为 String 类型,但看到这个 Map 中有这样的 key 你立刻想到的是什么?基础面试常常会问 equals 和 hashcode 的问题,下一篇文章会借着这个类来分析说明一下你总困惑的这件小事
  2. 对于 Spring Boot 的整个调用过程,你能描述出总体流程吗?
  3. Spring 内置多少个 Resolver?你能够跟踪调试获取到
  • Lombok 使用详解,简化Java编程
  • Java升级那么快,多个版本如何灵活切换和管理?
  • 手把手教你定制标准 Spring Boot starter
  • JDK12 Collectors.teeing 功能真香
  • Maven optional 关键字透彻图解

如何妙用Spring 数据绑定机制
如何妙用Spring 数据绑定机制

tan日拱一兵转发在看也很赞钟意做者

相关文章
相关标签/搜索