在工作中,经常会需要将对象转换成不同的形式来适应不同的api,例如对接第三方的接口,将数据落盘到自己的数据库中;在比如不通分层之间的对象转换,从业务层到表现层,有些字段是不需要展示的等。
进行这种转换,除了编写大量的 get/set 代码,像spring、apache也提供了很多的工具类可以实现。
-
BeanUtils
在 spring 和 apache 的 commons 工具包中都提供了 BeanUtils 供我们使用,其原理都是先用 jdk 的 java.beans.Introspector类的getBeanInfo()方法获取对象的属性信息及属性get/set方法,接着使用反射(Method的invoke(Object obj, Object… args))方法进行赋值。apache支持名称相同但类型不同的属性的转换,spring支持忽略某些属性不进行映射,他们都设置了缓存保存已解析过的BeanInfo信息。
Maven 依赖
Spring 是自带的, apache 的添加这个依赖<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
-
BeanCopier
cglib 的 BeanCopier采用了不同的方法:它不是利用反射对属性进行赋值,而是直接使用ASM的MethodVisitor直接编写各属性的get/set方法(具体过程可见BeanCopier类的generateClass(ClassVisitor v)方法)生成class文件,然后进行执行。由于是直接生成字节码执行,所以BeanCopier的性能较采用反射的BeanUtils有较大提高。
Maven 依赖<dependency> <groupId>asm</groupId> <artifactId>asm</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-commons</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-util</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2.2</version> </dependency>
相关代码
import java.util.concurrent.ConcurrentHashMap; import lombok.extern.slf4j.Slf4j; import net.sf.cglib.beans.BeanCopier; @Slf4j public class CGlibMapper { //使用缓存提高效率 private static final ConcurrentHashMap<String, BeanCopier> mapCaches = new ConcurrentHashMap<>(); public static <O, T> T mapper(O source, Class<T> target) { T instance = baseMapper(source, target); return instance; } public static <O, T> T mapper(O source, Class<T> target, IAction<T> action) { T instance = baseMapper(source, target); action.run(instance); return instance; } public static <O, T> T mapperObject(O source, T target) { String baseKey = generateKey(source.getClass(), target.getClass()); BeanCopier copier; if (!mapCaches.containsKey(baseKey)) { copier = BeanCopier.create(source.getClass(), target.getClass(), false); mapCaches.put(baseKey, copier); } else { copier = mapCaches.get(baseKey); } copier.copy(source, target, null); return target; } public static <O, T> T mapperObject(O source, T target, IAction<T> action) { String baseKey = generateKey(source.getClass(), target.getClass()); BeanCopier copier; if (!mapCaches.containsKey(baseKey)) { copier = BeanCopier.create(source.getClass(), target.getClass(), false); mapCaches.put(baseKey, copier); } else { copier = mapCaches.get(baseKey); } copier.copy(source, target, null); action.run(target); return target; } private static <O, T> T baseMapper(O source, Class<T> target) { String baseKey = generateKey(source.getClass(), target); BeanCopier copier; if (!mapCaches.containsKey(baseKey)) { copier = BeanCopier.create(source.getClass(), target, false); mapCaches.put(baseKey, copier); } else { copier = mapCaches.get(baseKey); } T instance = null; try { instance = target.getDeclaredConstructor().newInstance(); } catch (Exception e) { log.error("mapper 创建对象异常" + e.getMessage()); } copier.copy(source, instance, null); return instance; } private static String generateKey(Class<?> class1, Class<?> class2) { return class1.toString() + class2.toString(); } }
-
Orika
Orika 底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多.
Maven 依赖<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.2</version> </dependency>
测试一下:
import java.util.stream.IntStream;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StopWatch;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
public class BeanMain {
public static void main(String[] args) throws Exception {
int count = 2;
// BeanUtils
StopWatch stopWatch = new StopWatch("Bean Copy");
stopWatch.start("org.springframework.beans.BeanUtils");
Item item = new Item("MAC PRO", 18.1, 1);
IntStream.rangeClosed(1, count).forEach(i -> {
ItemDb itemDb = new ItemDb();
BeanUtils.copyProperties(item, itemDb);
});
stopWatch.stop();
stopWatch.start("org.apache.commons.beanutils.BeanUtils");
IntStream.rangeClosed(1, count).forEach(i -> {
ItemDb itemDb = new ItemDb();
try {
org.apache.commons.beanutils.BeanUtils.copyProperties(itemDb, item);
} catch (Exception e) {
e.printStackTrace();
}
});
stopWatch.stop();
// PropertyUtils
stopWatch.start("org.apache.commons.beanutils.PropertyUtils");
IntStream.rangeClosed(1, count).forEach(i -> {
ItemDb itemDb = new ItemDb();
try {
PropertyUtils.copyProperties(itemDb, item);
} catch (Exception e) {
e.printStackTrace();
}
});
stopWatch.stop();
// BeanCopier
stopWatch.start("BeanCopier");
IntStream.rangeClosed(1, count).forEach(i -> {
ItemDb itemDb = new ItemDb();
try {
CGlibMapper.mapperObject(item, itemDb);
} catch (Exception e) {
e.printStackTrace();
}
});
stopWatch.stop();
// Orika
stopWatch.start("Orika");
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Item.class, ItemDb.class)
.field("name", "name")
.field("price", "price")
.field("count", "count")
.byDefault()
.register();
MapperFacade mapper = mapperFactory.getMapperFacade();
IntStream.rangeClosed(1, count).forEach(i -> {
try {
ItemDb itemDb = mapper.map(item, ItemDb.class);
} catch (Exception e) {
e.printStackTrace();
}
});
stopWatch.stop();
// get/set
stopWatch.start("get/set");
IntStream.rangeClosed(1, count).forEach(i -> {
try {
ItemDb itemDb = new ItemDb();
itemDb.setName(item.getName());
itemDb.setCount(item.getCount());
itemDb.setPrice(item.getPrice());
} catch (Exception e) {
e.printStackTrace();
}
});
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
class Item {
private String name;
private double price;
private int count;
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
class ItemDb {
private String name;
private double price;
private int count;
private long createTime;
private long updateTime;
}
输出结果:
ns % Task name
---------------------------------------------
194850419 045% org.springframework.beans.BeanUtils
028169745 007% org.apache.commons.beanutils.BeanUtils
000965980 000% org.apache.commons.beanutils.PropertyUtils
032875495 008% BeanCopier
171359788 040% Orika
000248811 000% get/set
在对bean copy 性能要求不高的时候,使用哪个是无差别的,如上是在 copy 一个对象的时候的耗时。
可以看到 get/set 是最快的,BeanCopier 和 Orika并没有表现出比 BeanUtils和PropertyUtils更快的性能,那么将 count 改成100000 看下,BeanCopier 表现出了比 BeanUtils 更高的性能,同时 Orika 的性能也有提升,但是都比不上 get/set 方法,所以在性能要求不高的情况下,使用哪个都可以,如果对性能的要求特别高,建议还是使用 get/set,但是注意一定要保证字段映射的正确性。
ns % Task name
---------------------------------------------
349749328 016% org.springframework.beans.BeanUtils
980190590 045% org.apache.commons.beanutils.BeanUtils
468454769 022% org.apache.commons.beanutils.PropertyUtils
064587062 003% BeanCopier
307255908 014% Orika
002375458 000% get/set