文档章节

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

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

本篇是 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

© 著作权归作者所有

共有 人打赏支持
罗格林

罗格林

粉丝 270
博文 43
码字总数 41930
作品 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
JNA调用DLL函数遇到的几个问题

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

雷霄骅
2013/09/30
0
0
commons-beanutils

概述 commons-beanutil开源库是apache组织的一个基础的开源库,为apache中许多类提供工具方法,学习它是学习其他开源库实现的基础。 Commons-beanutil中包含大量和JavaBean操作有关的工具方法...

wuchongchang
2011/09/15
0
0
Apache Commons包简介

Components Description Latest Version Released BeanUtils 易于使用的包装在Java反射和内省API 1.9.2 2014-05-29 CLI 命令行参数解析 1.2 2009-03-19 Codec 一般的编码/解码算法(例如语音...

银月光海
2015/01/15
0
0
从 java bean 的内省到 dbutils 的应用

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

peiquan
07/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

70.shell的函数 数组 告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析 20.16/20.17 shell中的函数: ~1. 函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段...

王鑫linux
今天
2
0
分布式框架spring-session实现session一致性使用问题

前言:项目中使用到spring-session来缓存用户信息,保证服务之间session一致性,但是获取session信息为什么不能再服务层获取? 一、spring-session实现session一致性方式 用户每一次请求都会...

WALK_MAN
今天
5
0
C++ yield()与sleep_for()

C++11 标准库提供了yield()和sleep_for()两个方法。 (1)std::this_thread::yield(): 线程调用该方法时,主动让出CPU,并且不参与CPU的本次调度,从而让其他线程有机会运行。在后续的调度周...

yepanl
今天
4
0
Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)

这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情。并发课题对于Java来说是一个又重要又难的一大块,除非气定神闲、精力满满,否则我本身是不敢随便写...

心中的理想乡
今天
33
0
shell学习之获取用户的输入命令read

在运行脚本的时候,命令行参数是可以传入参数,还有就是在脚本运行过程中需要用户输入参数,比如你想要在脚本运行时问个问题,并等待运行脚本的人来回答。bash shell为此提 供了read命令。 ...

woshixin
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部