文档章节

Java OSGL 工具库 - Bean 拷贝的艺术

罗格林
 罗格林
发布于 06/14 13:37
字数 1810
阅读 2477
收藏 60

本篇是 Java OSGL 工具库系列的第六篇, 前面五篇分别是:

  1. 图片处理的艺术
  2. 图片处理的艺术之自定义图片处理器
  3. 字符串操作的艺术
  4. I/O 操作的艺术
  5. 类型转换的艺术

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 字段, 并将 foosubject 字段拷贝到 bartitle 字段.

2.3 过滤器

过滤器用来指定在拷贝过程中忽略某些字段. 下面是过滤器的一些例子:

  • -email,-password - 忽略 emailpassword 字段;其他所有字段都需要拷贝
  • +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<Foo> fooList = C.list(new Foo(), new Foo());
List<Bar> barList = C.newList();
$.map(fooList).targetGenericType(new TypeReference<List<Bar>>(){}).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 时, 需要将源数据 dateCalendar 类型转换到目标数据 dateDateTime 类型. 开发可以写一个类型转换器:

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

并在 API 调用中指定类型转换器:


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

注意

  • 在实际中你可能不需要定义很多类型转换器, 包括上面那个 CalendarDateTime 的, 因为 OSGL 已经默认提供了大部分需要用到的. 更多关于类型转换器的情况参见 类型转换的艺术
  • 类型转换仅适用于 MAPMERGE_MAP 语义, SHALLOW_COPY, DEEP_COPYMERGE 语义不支持类型转换

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<Class, Object>() {
    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 坐标:

<dependency>
  <groupId>org.osgl</groupId>
  <artifactId>osgl-tool</artifactId>
  <version>${osgl-tool.version}</version>
</dependency>

目前最新的 ${osgl-tool.version}1.17.0

© 著作权归作者所有

共有 人打赏支持
罗格林

罗格林

粉丝 286
博文 43
码字总数 41912
作品 4
其他
架构师
私信 提问
加载中

评论(13)

罗格林
罗格林

引用来自“衣舞晨风”的评论

有源码分享吗?
https://gitee.com/osglworks/java-tool
衣舞晨风
衣舞晨风
有源码分享吗?
罗格林
罗格林

引用来自“开源中国首席PHP宣传专家”的评论

OSGL 不是做模块化java的吗?可以用来模块化吗?

你说的是 OSGI, 不是 OSGL
开源中国首席罗纳尔多
开源中国首席罗纳尔多
OSGL 不是做模块化java的吗?可以用来模块化吗?
李嘉图
李嘉图
堪称完美,细节到位,完全对得起艺术这两个字
快速开发师
快速开发师
无论写的多好,都是增加学习成本,增加开发者的负担
罗格林
罗格林

引用来自“可爱的鱼”的评论

呃,能解释一下这个OSGL吗?百度搜不出来。
osgl - Open Source General Library 是一套开源工具库. 目前绝大部分都是 Java 的, 只有一个状态机工具是 C 的. 这个工具库的 repo 在 https://github.com/osglworks/, gitee 上也有, 只是目前 Gitee 上还不完全: https://gitee.com/osglworks
可爱的鱼
可爱的鱼
呃,能解释一下这个OSGL吗?百度搜不出来。
Artlongs
Artlongs
起初我抱怨 ACT 没有基本的bean copy 功能,提了个需求,想不到大神搞了个这么牛的工具类,666.
大神请收下我的膝盖。
罗格林
罗格林

引用来自“talent-tan”的评论

罗总写了这么多好博客,是不是该考虑一下出本书,我第一个买
谭总过奖了. 写书沉淀还差了些啊, 还需要多积累 ...
act-starters-1.8.8.6 发布 - 更加易用的 AAA 集成方案

ActFramework 是一款专注于代码表达力的高性能 Java MVC/RESTful 全栈框架. 主要更新: 更加易用的 AAA (认证, 授权, 记账) 集成 - 无需再写 Adaptor 类 全面升级的 e2e 支持 - 包括自动继承 ...

罗格林
06/22
0
0
精心打磨的 Act-1.8.8 出炉了

11 个 RC 版本, 6个月的精心打磨, ActFramework 1.8.8 终于出炉了. 这次版本带来了 220 个错误修复或改进, 其中最主要的特性是 自动化测试支持 数据库访问的增强,包括简化事务的使用以及对 ...

罗格林
11/01
0
0
JNA调用DLL函数遇到的几个问题

最近一个JSP项目需要用到分词模块,而分词模块实用C++写成的DLL库。于是上网搜各种方法,最后选择了JNA作为JSP调用DLL的工具。 JNA(Java Native Access )提供一组Java工具类用于在运行期动...

雷霄骅
2013/09/30
0
0
从 java bean 的内省到 dbutils 的应用

java bean 内省的基础 java bean 的内省,其实可以算是反射的一种基础应用,关于 java 的反射,无非就是获得对应的类、属性、方法、修饰符等的应用,对于 java 的反射探讨,可以点击参考 ja...

peiquan
07/04
0
0
聊一聊Spring中的线程安全性

Spring与线程安全 Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。 Spring对每个...

SylvanasSun
10/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

经典编程书籍大全·我一本都没有看过

经典编程书籍大全 100+ 经典技术书籍,涵盖:计算机系统与网络、系统架构、算法与数据结构、前端开发、后端开发、移动开发、数据库、测试、项目与团队、程序员职业修炼、求职面试 和 编程相关...

netkiller-
19分钟前
0
0
改变自己从学习linux开始

刚刚高中毕业,进如大学的时候,总以为摆脱了束缚可以无拘无束的玩耍了。当时真的就是和众多大学生一起,像撒欢的野马,每天逃课,上网,泡吧,不把学习当一会事,学校里教授讲的各种知识也没...

linuxprobe16
22分钟前
2
0
Apache Zeppelin 中 Spark解释器

概述 Apache Spark是一种快速和通用的集群计算系统。它提供Java,Scala,Python和R中的高级API,以及支持一般执行图的优化引擎。Zeppelin支持Apache Spark,Spark解释器组由5个解释器组成。 ...

hblt-j
23分钟前
0
0
十分钟带你理解Kubernetes核心概念

http://www.dockone.io/article/932

踏破铁鞋无觅处
36分钟前
1
0
浅析微信支付:开通免充值产品功能及如何进行接口升级指引

本文是【浅析微信支付】系列文章的第十五篇,主要讲解如何开通免充值产品功能流程和其中的注意事项,对于接口升级会重要讲解,避免爬坑。 浅析微信支付系列已经更新十五篇了哟~,没有看过的...

YClimb
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部