Java OSGL 工具庫-Bean 拷貝的藝術

1. API 一覽

// shallow copy from `foo` to `bar`$.copy(foo).to(bar);// deep copy from `foo` to `bar$.deepCopy(foo).to(bar);// deep copy using loose name match$.deepCopy(foo).looseMatching().to(bar);// deep copy with filter$.deepCopy(foo).filter("-password,-address.streetNo").to(bar);// deep copy with special name mapping rule$.deepCopy(foo) .map("id").to("no") .map("subject").to("title") .to(bar);// merge data from `foo` to `bar`$.merge(foo).to(bar);// map data from `foo` to `bar`$.map(foo).to(bar);// map data from `foo` to `bar` using strict name match$.map(foo).strictMatching().to(bar);// merge map data from `foo` to `bar`$.mergeMap(foo).to(bar);

2. 概念

OSGL 依賴於 Java 反射來獲得 Bean 的內部結構. 和很多其他工具不同, OSGL 使用字段而不是 Getter/Setter 來獲取內部數據

2.1 語義

OSGL 提供一下五種不同的 Bean 拷貝語義:

  1. SHALLOW_COPY - 淺拷貝: 拷貝第一層字段的引用. API:
  • $.copy(foo).to(bar)
  1. DEEP_COPY- 深拷貝: 遞歸拷貝過程直到遇到不可變 (Immutable) 類型. API:
  • $.deepCopy(foo).to(bar)
  1. MERGE - 融合: 和深拷貝類似, 但碰到數組時容器的情況將源數據添加到目標容器. API:
  • $.merge(foo).to(bar)
  1. MAP - 印射: 和深拷貝類似, 並支持數據類型轉換. API:
  • $.map(foo).to(bar)
  1. MERGE_MAP - 融合印射: 和融合類似, 並支持數據類型轉換. API:
  • $.mergeMap(foo).to(bar)

2.1.1 不可變類型

不可變類型在 OSGL Bean 深度拷貝過程中是非常重要的概念. 當 OSGL 發現拷貝的數據類型為不可變時, 深度拷貝過程將終止, 並直接將數據引用拷貝到目標 Bean.

OSGL 認定以下類型為不可變類型:

  • 所有的基本數據類型及其包裝類型
  • 字符串
  • 枚舉
  • 所有使用 OsglConfig.registerImmutableClassNames API 註冊的類型
  • 所有使用 OsglConfig.registerImmutableClassPredicate($.Predicate) API 註冊的判定函數返回 true 的類型

2.2 名字匹配

OSGL 支持數據名字匹配

  1. 嚴格匹配, 源數據和目標數據的字段名必須完全一致. 這是默認匹配方式.
  2. Keyword 匹配, 也叫 loose 匹配. 當採用這種匹配方式的時候, 下面的名字被認定為相匹配的名字:
  • foo_bar
  • foo-bar
  • fooBar
  • FooBar
  • Foo-Bar
  • Foo_Bar
  1. 特殊匹配

下面是一段特殊匹配的示例代碼:

$.deepCopy(foo) .map("id").to("no") .map("subject").to("title") .to(bar);

上面代碼指示 OSGL 將 foo 對象的 id 字段拷貝到 bar 對象的 no 字段, 並將 foo 的 subject 字段拷貝到 bar 的 title 字段.

2.3 過濾器

過濾器用來指定在拷貝過程中忽略某些字段. 下面是過濾器的一些例子:

  • -email,-password - 忽略 email 和 password 字段;其他所有字段都需要拷貝
  • +email - 僅拷貝 email 字段, 其他所有字段都不拷貝
  • -cc.cvv- 忽略 cc 字段對象的 cvv 字段, 其他所有字段都拷貝
  • -cc,+cc.cvv - 對於 cc 字段對象, 僅拷貝其 cvv 字段, 忽略其他所有字段

使用過濾器的 API:

$.deepCopy(foo).filter("-password,-address.streetNo").to(bar);

注意 過濾器匹配目標對象, 而非源對象

2.4 根類型

上面我們有提到 OSGL 依賴於字段來獲得拷貝數據. 因為 Java 類型繼承的原因, 獲取字段是一個遞歸過程直到遇到 Object.class. 有時候我們希望遞歸過程更早結束, 這個時候可以指定根類型. 假設我們有下面的類:

public abstract class ModelBase { public Date _created;}

假設拷貝源的類型是 ModelBase 的子類, 而你的 Dao 使用 ModelBase._created 來判斷某個 Entity 是新建記錄, 還是從數據庫中加載的老記錄. 這種情況下, 當你需要拷貝某個老記錄 Bean 到一個新記錄 Bean, 並使用 Dao 來保存這個新建記錄的時候就需要注意不能拷貝 ModelBase._created 字段. 因此需要指定根類型:

MyModel copy = $.copy(existing).rootClass(ModelBase.class).to(MyModel.class);

2.5 目標的泛型

當目標對象是一個泛型, 例如容器時, 需要提供 targetGenericType 來完成拷貝:

List fooList = C.list(new Foo(), new Foo());List barList = C.newList();$.map(fooList).targetGenericType(new TypeReference>(){}).to(barList);

2.6 類型轉換

使用印射語義進行拷貝時, OSGL 自動在源數據類型和目標數據類型之間做轉換. 假設源 Bean 定義為:

public class RawData { Calendar date; public RawData(long currentTimeMillis) { date = Calendar.getInstance(); date.setTimeInMillis(currentTimeMillis); }}

目標 Bean 定義為:

public static class ConvertedData { DateTime date;}

當我們需要將 RawData 拷貝到 ConvertedData 時, 需要將源數據 date 從 Calendar 類型轉換到目標數據 date 的 DateTime 類型. 開發可以寫一個類型轉換器:

public static Lang.TypeConverter converter = new Lang.TypeConverter() { @Override public DateTime convert(Calendar calendar) { return new DateTime(calendar.getTimeInMillis()); }};

並在 API 調用中指定類型轉換器:

@Testpublic void testWithTypeConverter() { RawData src = new RawData($.ms()); ConvertedData tgt = $.map(src).withConverter(converter).to(ConvertedData.class); eq(tgt.date.getMillis(), src.date.getTimeInMillis());}

注意

  • 在實際中你可能不需要定義很多類型轉換器, 包括上面那個 Calendar 到 DateTime 的, 因為 OSGL 已經默認提供了大部分需要用到的. 更多關於類型轉換器的情況參見 類型轉換的藝術
  • 類型轉換僅適用於 MAP 和 MERGE_MAP 語義, SHALLOW_COPY, DEEP_COPY 和 MERGE 語義不支持類型轉換

2.6.1 轉換提示

有的情況需要給出轉換提示來幫助類型轉換正確進行. 一個典型的例子是從字符串轉換為日期, 這個過程需要提供日期格式作為轉換提示. 另一個例子是從字符串轉換為整型, 可以提供 radix 轉換提示來調整轉換過程. 下面是一個字符串到日期類型轉換的案例.

源 Bean 定義:

public static class RawDataV2 { String date; public RawDataV2(String date) { this.date = date; }}

目標 Bean 定義:

public static class ConvertedDataV2 { Date date;}

使用轉換提示進行源 Bean 到目標 Bean 拷貝:

RawDataV2 src = new RawDataV2("20180518");ConvertedDataV2 tgt = $.map(src).conversionHint(Date.class, "yyyyMMdd").to(ConvertedDataV2.class);

從代碼中我們看到轉換提示 yyyyMMdd 和目標數據類型 Date.class 綁定在一起, 這是告訴 OSGL, 當轉換目標類型為 Date 的時候, 使用轉換提示 yyyyMMdd.

2.7 實例工廠

在拷貝過程中, 有可能需要就某個類型創建實例. 默認情況下 OSGL 使用 org.osgl.Lang.newInstance(Class) API 來創建實例. 有的環境下, 例如當應用運行在 ActFramework 下的時候, 需要將實例創建交給容器 API Act.newInstance(Class). OSGL 提供 API 來註冊實例工廠來替代默認創建實例的邏輯:

OsglConfig.registerGlobalInstanceFactory(new $.Function() { final App app = Act.app(); @Override public Object apply(Class aClass) throws NotAppliedException, $.Break { return app.getInstance(aClass); }});

4. 總結

OSGL 提供了一套功能完善的 API 支持 Bean 的拷貝操作, 包括 5 種拷貝語義. 如果您喜歡 OSGL, 這裡是 maven 座標:

 org.osgl osgl-tool ${osgl-tool.version}作者:羅格林


分享到:


相關文章: