如何妙用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日拱一兵轉發在看也很贊鐘意做者

相關文章
相關標籤/搜索