// 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);
OSGL 依赖于 Java 反射来获得 Bean 的内部结构. 和很多其他工具不同, OSGL 使用字段而不是 Getter/Setter 来获取内部数据
OSGL 提供一下五种不同的 Bean 拷贝语义:
$.copy(foo).to(bar)
$.deepCopy(foo).to(bar)
$.merge(foo).to(bar)
$.map(foo).to(bar)
$.mergeMap(foo).to(bar)
不可变类型在 OSGL Bean 深度拷贝过程中是非常重要的概念. 当 OSGL 发现拷贝的数据类型为不可变时, 深度拷贝过程将终止, 并直接将数据引用拷贝到目标 Bean.
OSGL 认定以下类型为不可变类型:
OsglConfig.registerImmutableClassNames
API 注册的类型OsglConfig.registerImmutableClassPredicate($.Predicate)
API 注册的判定函数返回 true
的类型OSGL 支持数据名字匹配
Keyword
匹配, 也叫 loose
匹配. 当采用这种匹配方式的时候, 下面的名字被认定为相匹配的名字:下面是一段特殊匹配的示例代码:
$.deepCopy(foo)
.map("id").to("no")
.map("subject").to("title")
.to(bar);
上面代码指示 OSGL 将 foo
对象的 id
字段拷贝到 bar
对象的 no
字段, 并将 foo
的 subject
字段拷贝到 bar
的 title
字段.
过滤器用来指定在拷贝过程中忽略某些字段. 下面是过滤器的一些例子:
-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);
当目标对象是一个泛型, 例如容器时, 需要提供 targetGenericType
来完成拷贝:
List<Foo> fooList = C.list(new Foo(), new Foo());
List<Bar> barList = C.newList();
$.map(fooList).targetGenericType(new TypeReference<List<Bar>>(){}).to(barList);
使用印射语义进行拷贝时, 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<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());
}
注意
Calendar
到 DateTime
的, 因为 OSGL 已经默认提供了大部分需要用到的. 更多关于类型转换器的情况参见 类型转换的艺术MAP
和 MERGE_MAP
语义, SHALLOW_COPY
, DEEP_COPY
和 MERGE
语义不支持类型转换有的情况需要给出转换提示来帮助类型转换正确进行. 一个典型的例子是从字符串转换为日期, 这个过程需要提供日期格式作为转换提示. 另一个例子是从字符串转换为整型, 可以提供 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
.
在拷贝过程中, 有可能需要就某个类型创建实例. 默认情况下 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);
}
});
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