文章目录
Chameleon 是一款基于 javassist 动态字节码生成的高性能类型转换工具。
拥有比Spring的BeanUtils更高的性能。
通过动态加载类型转换类的方式,使Chameleon拥有很高的类型转换性能。
解决问题
- Spring 的 BeanUtils 类型转换效率相对不太理想;
- MapStruct 配置太过复杂;
原理
- 在首次转换两个类时,通过 javassist 生成两个类型之间相互转化的字节码类,加载到JVM中,并缓存下来;
- 根据两个对象的Class,找到缓存中转化两者的动态实现类,调用生成的方法,完成转换。
原理跟 MapStruct 相似,生成的转换类中使用Getter/Setter进行赋值,MapStruct 和 Chameleon 的效率相当;
不同的是,MapStruct 编译时生成转换类,Chameleon 运行时根据需要动态生成两者相互转换类;
Chameleon在惰性加载模式下,首次动态生成字节码并加载需要150ms左右;可以通过添加注解或者自定义适配选择器,来预加载类型转换类。
支持转换的情景
仅处理具有 getter/setter/is 函数的属性
-
类型相同,直接转换
1.1 类型为List<?>且泛型类相同,直接转换
1.2 类型为List<?>且泛型类不同,转换泛型类,再赋值
1.3 类型为List<?>且泛型类不同,目标值是String,原值不为空的情况下,将原值 toString 处理
-
类型不同,转换类型,再赋值
2.1 如果目标值是String,原值不为空的情况下,将原值 toString 处理
使用
dependency
<dependency>
<groupId>cn.muzin</groupId>
<artifactId>chameleon</artifactId>
<version>1.0.1</version>
</dependency>
方式1(惰性加载)
不预加载转换类,在需要的时候加载转换类
// 1. 根据 Class 进行转换
AStruct aStruct = new AStruct();
// ignore aStruct Code ...
BStruct bStruct = ChameleonUtil.transform(aStruct, BStruct.class);
// 第三个参数为true时,子类型不一致,但字段相同,也可以转换
BStruct bStruct = ChameleonUtil.transform(aStruct, BStruct.class, true);
// 2. 值拷贝
BStruct bStruct1 = new BStruct();
ChameleonUtil.transform(aStruct, bStruct1);
// 3. 按照 Class,进行集合的转换
List<AStruct> aStructList = new ArrayList<AStruct>();
List<BStruct> bStructList = ChameleonUtil.transform(aStructList, BStruct.class);
方式2(通过注解预加载)
在需要转换的类上面标记@ChameleonTransform
注解,通过配置ChameleonTransformEnvironmentAdaptSelector
选择器,来预加载类型互转的转换类
注意:配置完成后,一定要调用
ready
方法!!!
// 添加 @ChameleonTransform 注解
@ChameleonTransform(dest = { BStruct.class, OtherStruct.class })
public class AStruct extends CStruct {
// class code ...
}
// 配置 注解适配选择器, 扫描指定包下面的所有类(可添加多个包名)
ChameleonUtil.addEnvironmentAdaptSelector(
new ChameleonTransformEnvironmentAdaptSelector()
.addBasePackage("cn.muzin.chameleon")
);
// 告诉 Chameleon 已经准备好了,开始配置组建。
ChameleonUtil.ready();
// 开始转换目标对象...
AStruct aStruct = new AStruct();
// ignore aStruct Code ...
BStruct bStruct = ChameleonUtil.transform(aStruct, BStruct.class);
方式3(自定义预加载)
实现EnvironmentAdaptSelector
接口,自定义加载类型转换规则。
根据自己的需要返回类型之间的1对1、1对多关系。
// 根据需要返回相应的结构对
StructPair 结构对
+- StructToOnePair 结构1对1
+- StructToMultiPair 结构1对多
示例:
// 实现 EnvironmentAdaptSelector 接口
public class EnvironmentAdaptSelectorImpl implements EnvironmentAdaptSelector {
public List<StructPair> selector() {
// your code...
return new ArrayList<StructPair>();
}
}
// 通过 Chameleon 或者 ChameleonUtil 添加该适配选择器
ChameleonUtil.addEnvironmentAdaptSelector(
new EnvironmentAdaptSelectorImpl()
);
// 告诉 Chameleon 已经准备好了,开始配置组建。
ChameleonUtil.ready();
// 开始转换目标对象...
AStruct aStruct = new AStruct();
// ignore aStruct Code ...
BStruct bStruct = ChameleonUtil.transform(aStruct, BStruct.class);
代码演示
以下@Data
为lombok的@Data
注解,不想使用lombok,可以将@Data去掉,生成Getter/Setter.
@ChameleonTransform
在使用注解适配选择器时配置,可以不配置注解进行惰性加载,或者根据自定义适配选择器进行预加载。
AStruct.java
@ChameleonTransform(dest = { BStruct.class })
@Data
public class AStruct extends CStruct {
private String name;
private String age;
private String weight;
private String height;
private String idcard;
private String school;
private String profile;
private String photo;
private AInnerStruct inner;
private List<AInnerStruct> innerList;
private Integer ttt;
private List<Long> signList;
private List<Long> strList;
}
BStruct.java
@Data
public class BStruct extends CStruct {
private String name;
private String age;
private String weight;
private String height;
private String idcard;
private String school;
private String profile;
private String photo;
private BInnerStruct inner;
private List<BInnerStruct> innerList;
private String ttt;
private List<String> signList;
private List<Long> strList;
}
CStruct.java
@Data
public class CStruct {
private String namec;
}
AInnerStruct.java
@ChameleonTransform(dest = { BInnerStruct.class })
@Data
public class AInnerStruct {
private String ppp;
}
BInnerStruct.java
@Data
public class BInnerStruct {
private String ppp;
}
ChameleonUtil.transform(struct, class, Boolean)
函数 的第三个值用于控制类型不匹配也尝试进行转换。
如上:AStruct
和BStruct
中存在inner
和innerList
, 在 第三个值为true
的情况下,内部类型不匹配也可以进行转换。
示例:
long st = 0;
long et = 0;
// 设置 class 文件存储位置,不设置时,默认在临时目录下,便于debug时,查看class的情况。
ChameleonUtil.setTmpDir("/Users/sirius/bucket/project/IdeaProjects/chameleon/target/dclass");
// 配置 注解适配选择器
ChameleonUtil.addEnvironmentAdaptSelector(
new ChameleonTransformEnvironmentAdaptSelector()
.addBasePackage("cn.muzin.chameleon")
);
st = System.currentTimeMillis();
// 开始根据注解 预加载 转换类
ChameleonUtil.ready();
et = System.currentTimeMillis();
System.out.println((et - st) + " ms ready");
AStruct aStruct = new AStruct();
AInnerStruct aInnerStruct = new AInnerStruct();
aInnerStruct.setPpp("asdf");
aStruct.setAge("23");
aStruct.setName("23");
aStruct.setHeight("23");
aStruct.setIdcard("23");
aStruct.setPhoto("23");
aStruct.setSchool("23");
aStruct.setWeight("23");
aStruct.setNamec("23");
aStruct.setInner(aInnerStruct);
aStruct.setTtt(123);
List<Long> longs = new ArrayList<Long>();
longs.add(123L);
longs.add(127L);
longs.add(125L);
aStruct.setSignList(longs);
aStruct.setStrList(longs);
ArrayList<AInnerStruct> aInnerStructs = new ArrayList<>();
aInnerStructs.add(aInnerStruct);
aInnerStructs.add(aInnerStruct);
aStruct.setInnerList(aInnerStructs);
for(int o = 0; o < 10; o++) {
st = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
BStruct bStruct = ChameleonUtil.transform(aStruct, BStruct.class, true);
}
et = System.currentTimeMillis();
System.out.println((et - st) + " ms transform");
}
结果: