1. 程式人生 > >如何妙用Spring 資料繫結機制?

如何妙用Spring 資料繫結機制?

前言

在剖析完 「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 資料機制

前言 在剖析完 「Spring Boot 統一資料格式是怎麼實現的? 」文章之後,一直覺得有必要說明一下 Spring's Data Binding Mechanism 「Spring 資料繫結機制」。 預設情況下,Spring 只知道如何轉換簡單資料型別。比如我們提交的 int、String 或 boole

JS學習筆記——AngularJS 1.x雙向資料機制

0.前言 AngularJS和vueJS是前端比較熱門的兩個框架,AngularJS 1.x是我第一個接觸的框架,雙向繫結是其最大的特點,我們從原生JS的角度看看,這個雙向資料繫結是如何實現的。點這裡看vueJS的雙向繫結原理。 1.簡單的雙向繫結實現

再談angularJS資料機制及背後原理—angularJS常見問題總結

這篇是對angularJS的一些疑點回顧,是對目前angularJS開發的各種常見問題的整理彙總。如果對文中的題目全部瞭然於胸,覺得對整個angular框架應該掌握的七七八八了。希望志同道合的通知補充內容Angular 的資料繫結採用什麼機制,詳述原理?髒檢查機制。闡釋髒檢查

Spring MVC 資料和表單標籤庫

資料繫結是將使用者輸入繫結到領域模型的一種特性。 資料繫結的好處: 1. 型別總是為 String 的 HTTP 請求引數,可用於填充不同型別的物件屬性。 2. 當輸入驗證失敗時,會重新生成一個 HTML 表單。 為了高效的使用資料繫結,還需要 Spring 的表單標籤庫。表單標籤庫中包含了可以用在

Spring MVC 資料流程分析

1.    資料繫結流程原理★ ①   Spring MVC 主框架將 ServletRequest  物件及目標方法的入參例項傳遞給 WebDataBinderFactory 例項,以建立 DataBinder 例項物件 ②

Vue 更新機制資料

一、更新機制。 參考帥氣的別人家博主:更新機制! 二、資料繫結 原理: vue主要是藉助物件的訪問器屬性(Object.defineProperty)劫持資料,並結合訂閱者-釋出者模式來實現資料雙向繫結。 通過Object.defineProperty把data中的各資料屬性改為訪問

WPF資料-XAML

用XAML繫結          WPF元素不僅是資料繫結的目標,它還可以是繫結的源。可以把一個WPF元素的源屬性繫結到另一個WPF元素的目標屬性上。 ElementName指向繫結源,Path指向源屬性 此處CheckBox為繫結源,IsChecked為源屬性,Labe

Spring 5 官方文件》5. 驗證、資料和型別轉換

原文連結 譯者:14shadow43 5 驗證、資料繫結和型別轉換 5.1 介紹 JSR-303/JSR-349 Bean Validation 在設定支援方面,Spring Framework 4.0支援Bean Validation 1.0(JSR-303)和Bean Validation

Spring MVC---資料和表單標籤

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transi

從資料庫獲取到json資料,前端vue.js資料

function userinfor() { $.get("http://127.0.0.1:8082/lzghcg/user/userShows", function(result, state) { //這裡顯示從伺服器返回的資料 new Vue

spring mvc 中通過controller 傳遞物件給jsp,並且資料,在修改值後回傳物件給controller

在controller 中需要指定 sessionAttribute的key @sessionattributes註解應用到Controller上面,可以將Model中的屬性同步到session當中。 當需要清除session當中的值得時候,我們只需要在

spring mvc 資料 400錯誤

情景:使用在方法中繫結資料的時候,開啟連結,出現400錯誤。 @RequestMapping(value = "editItemSubmit") public String editItemSubmit(int id, Items item) {

Spring MVC 自定義資料 報http 406錯誤

前臺時間(如2013-08-12 18:10:23)傳到後臺srpingMVC 進行繫結到javaBean的util.date 時會報資料繫結失敗,不能從String 轉換到Date 型別。 現在我寫了一個自定議資料繫結類 package com.ltkj.zhepg.

spring mvc 資料問題 提交表單提示HTTP status 400, The request sent by the client was syntactically incorrect

我們在spring mvc 中controller方法中的引數,spring mvc會自動為我們進行資料繫結。 spring mvc 方法中不一定要全部都有 form表單提交的屬性, 也可以有 請求屬性中 沒有的引數(這時候只會把對應不上的引數設為null),

spring mvc 多個bean,或一個bean多個物件的資料

一、前臺傳遞不同類不同物件 1、屬性名不同,可直接封裝進controller方法的物件引數(經驗證) 2、屬性名有重複,可在重複的類中設定一個值型別,後臺再去將值型別值賦值給例項變數(經驗證) 二、同一類多個物件集合 方法1、Json方式 方法2、新建一個類,該

spring mvc使用@InitBinder 標籤對錶單資料

在SpringMVC中,bean中定義了Date,double等型別,如果沒有做任何處理的話,日期以及double都無法繫結。 解決的辦法就是使用spring mvc提供的@InitBinder標籤 在我的專案中是在BaseController中增加方法initBinde

最全面闡述WebDataBinder理解Spring資料

每篇一句 不要總問低階的問題,這樣的人要麼懶,不願意上網搜尋,要麼笨,一點獨立思考的能力都沒有 相關閱讀 【小家Spring】聊聊Spring中的資料繫結 --- DataBinder本尊(原始碼分析) 【小家Spring】聊聊Spring中的資料繫結 --- 屬性訪問器PropertyAccessor和

vue基礎4-資料

    1、v-bind 只能實現資料額單向繫結,從M到V,無法實現資料的雙向繫結       改變頁面輸入框的值,列印資料並未改變。      2、v-model 可以實現資料的雙向繫結,從M到V、V到M。 &nbs

Windows 7下arp命令IP和MAC地址,提示“ARP 項新增失敗: 拒絕訪問”的解決方法

在Win 7版本以管理員身份執行時提示:“ARP 項新增失敗:請求的操作需要提升。”    解決辦法: CMD中輸入:netsh i  i show in  //注意兩個i之間是有空格的 然後找到“本地連線”對應的 “Idx” (我的是

關於SAP UI5資料我的一些原創內容

如何查詢SAP UI5官方關於資料繫結的文件: https://sapui5.hana.ondemand.com/ 點Documentation: Filter裡輸入data就能看到Data Binding的文件了。 下面是一些我的原創文章。 Jerry寫過一個如何自學UI5框架的系列文章,一共包含