一、Orika背景介绍
Orika是java Bean映射框架,可以实现从一个对象递归拷贝数据至另一个对象。在开发多层应用程序中非常有用。在这些层之间交换数据时,通常为了适应不同API需要转换一个实例至另一个实例。
有很多方法可以实现:硬代码拷贝或Dozer实现bean映射等。总之,需要简化不同层对象之间映射过程。
Orika使用字节码生成器创建开销最小的快速映射,比其他基于反射方式实现(如,Dozer)更快。之前使用Bean Copy 性能非常慢,发现在这个领域业界还是有很多新秀的。 Orika 应该就算一个比较好的吧。
二、优势
1. 性能
大概是Dozer的8-10 倍, 这个上面的已经做了描述
2. 内存消耗
大概是Dozer内存消耗的一半多点。 为什么做到这点的还没想清楚, 估计是因为运行期不需要维护复杂的Mapping 关系。 不需要大量的Mapping 关系查找以及需要的对这些查找优化所消耗的空间。
3. 简单
Orika的代码短小精悍, 而且可读性非常强, Dozer如果要加减一个功能, 不才完全没有信心, Orika 我还是可以偶尔在Orika里面打几个酱油的。
三、基础使用
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.6</version>
</dependency>
UserVo对象
@Data //Set GET方法
@Accessors(chain = true)
public class UserVo {
private String id;
private String name;
}
User对象
@Data
@Accessors(chain = true)
public class User {
private String id;
private String name;
}
//业务场景,将A对象复制B对象中
//1.普通逻辑处理
User A = new User().setId("123").setName("1231");
UserVo B = new UserVo().setId(A.getId()).setName(A.getName());
System.out.println("普通方式将A对象复制B对象中:"+B);
//使用orika复制工具将A复制到B对象中
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
UserVo map = mapperFactory.getMapperFacade().map(A, UserVo.class);
System.out.println("orika复制对象:"+map);
2.A集合复制到B集合(集合复制)
//1.普通逻辑处
//A对象
List<User> A = Arrays.asList(new User().setId("123").setName("张三"));
//B对象
List<UserVo> B = new ArrayList<>();
//将A集合数据复制到B集合中
A.forEach(x->{
B.add( new UserVo().setId(x.getId()).setName(x.getName()));
});
System.out.println("将A集合中数据set到B集合中数据打印");
//使用orika复制工具将A集合复制到B集合中
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
List<UserVo> userVo = mapperFactory.getMapperFacade().mapAsList
mapperFactory.getMapperFacade().mapAsList(A, UserVo.class);
System.out.println("orika直接复制对象集合打印结果:"+userVo);
四、高级使用方法
UserVo对象
@Data //Set GET方法
@Accessors(chain = true)
public class UserVo {
private String id;
private String userName;
private int ageOne;
}
User对象
@Data
@Accessors(chain = true)
public class User {
private String id;
private String name;
private int age;
}
1.A对象复制到B对象,对象中属性不一样时
//业务场景,将A对象复制B对象中,A对象中的字段与B对象中的字段不一致
//1.普通逻辑处理
User A = new User().setId("123").setName("张三").setAge(20);
UserVo B = new UserVo().setId(A.getId()).setUserName(A.getName()).setAgeOne(A.getAge());
System.out.println("普通方式将A对象处理B对象中:"+B);.
//使用orika复制工具将A复制到B对象中
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(User.class, UserVo.class)
.field("name", "userName")
.field("age", "ageOne")
.byDefault().register();
UserVo userVo = mapperFactory.getMapperFacade().map(A, UserVo.class);
System.out.println("orika复制对象:"+userVo);
2.A集合对象复制到B集合对象,对象中属性不一样时
//1.普通逻辑处里A对象中与B对象字段不一致处理
//A对象
List<User> A = Arrays.asList(new User().setId("123").setName("张三").setAge(20));
//B对象
List<UserVo> B = new ArrayList<>();
//将A集合数据复制到B集合中
A.forEach(x->{
B.add(new UserVo().setId(x.getId()).setUserName(x.getName()).setAgeOne(x.getAge()));
});
System.out.println("将A集合中数据set到B集合中数据打印"+B);
//使用orika复制工具将A集合复制到B集合中
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(User.class, UserVo.class)
.field("name", "userName")
.field("age", "ageOne")
.byDefault().register();
List<UserVo> userVos = mapperFactory.getMapperFacade().mapAsList(A, UserVo.class);
System.out.println("orika复制对象:"+userVos);
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(User.class, UserVo.class)
.field("name", "userName")
.field("age", "ageOne")
.byDefault().register();
//集合复制--使用mapAsList
List<UserVo> userVos = mapperFactory.getMapperFacade().mapAsList(A, UserVo.class);
//对象复制--使用map
UserVo userVos = mapperFactory.getMapperFacade().map(A, UserVo.class);
针对复杂类型,例如枚举或者是json格式的转化。
// 这里假设我有这样两个类 BookEntity.java 和 BookDTO.java
public class BookEntity {
private String bookName;
private String authorName;
private Date authorBirthday;
// 一个Json字符串
private String bookInformation;
private Byte type;
// setter and getter
.......
}
public class BookDTO {
//
private String bookName;
// 有两个属性 name 和 birthday
private AuthorDTO author;
// 一个枚举类型
private BookType bookType;
// 一个类包含 ISBN 和 page
private BookInfo bookInfo;
// setter and getter
... ...
}
即使是采用了上面已有的方式,会发现枚举和BookInfo字段的值还是为null。
这个时候需要额外多做一些配置才能让orika
找到对应的数据了。
思路是:先注册转化处理的converter,然后再调用注册对象。
首先是 BookType
和 java.lang.Byte
类型的转换,利用BidirectionalConverter
类,定义他们的转换关系:
mapperFactory
.getConverterFactory()
.registerConverter("bookTypeConvert", new BidirectionalConverter<BookType, Byte>() {
@Override
public Byte convertTo(BookType bookType, Type<Byte> type, MappingContext mappingContext) {
return bookType.getVal();
}
@Override
public BookType convertFrom(Byte aByte, Type<BookType> type, MappingContext mappingContext) {
return BookType.getSelf(aByte);
}
});
这样orika
每次看到 BookType
和 Byte
之间的转化就会使用这个转换器来“拷贝”值了。
对于Json 和 String类型的转换,我定义了一个更加通用的Convert
,这里Json的序列化工具使用的是fastjson
public class JsonConfigConvert<T> extends BidirectionalConverter<T, String> {
@Override
public String convertTo(T source, Type<String> destinationType, MappingContext mappingContext) {
return JSON.toJSONString(source);
}
@Override
public T convertFrom(String source, Type<T> destinationType, MappingContext mappingContext) {
return JSON.parseObject(source, destinationType.getRawType());
}
}
在配置中这样写:
mapperFactory
.getConverterFactory()
.registerConverter("bookInfoConvert", new JsonConfigConvert<BookInfo>());
mapperFactory
.classMap(BookEntity.class, BookDTO.class)
.field("authorName", "author.name")
.field("authorBirthday", "author.birthday")
.fieldMap("type", "bookType").converter("bookTypeConvert").add()
.fieldMap("bookInformation", "bookInfo").converter("bookInfoConvert").add()
.byDefault()
.register();
再执行刚刚的代码就能得到完整的类了:
相比项目中大量的setter和getter,利用orika
这种操作方式显然会节约大量的代码。
五、总结工具类
/**
* 映射工具类
*/
public enum MapperUtils {
INSTANCE;
/**
* 默认字段工厂
*/
private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
/**
* 默认字段实例
*/
private static final MapperFacade MAPPER_FACADE = MAPPER_FACTORY.getMapperFacade();
/**
* 默认字段实例集合
*/
private static Map<String, MapperFacade> CACHE_MAPPER_FACADE_MAP = new ConcurrentHashMap<>();
/**
* 映射实体(默认字段)
*
* @param toClass 映射类对象
* @param data 数据(对象)
* @return 映射类对象
*/
public <E, T> E map(Class<E> toClass, T data) {
return MAPPER_FACADE.map(data, toClass);
}
/**
* 映射实体(自定义配置)
*
* @param toClass 映射类对象
* @param data 数据(对象)
* @param configMap 自定义配置
* @return 映射类对象
*/
public <E, T> E map(Class<E> toClass, T data, Map<String, String> configMap) {
MapperFacade mapperFacade = this.getMapperFacade(toClass, data.getClass(), configMap);
return mapperFacade.map(data, toClass);
}
/**
* 映射集合(默认字段)
*
* @param toClass 映射类对象
* @param data 数据(集合)
* @return 映射类对象
*/
public <E, T> List<E> mapAsList(Class<E> toClass, Collection<T> data) {
return MAPPER_FACADE.mapAsList(data, toClass);
}
/**
* 映射集合(自定义配置)
*
* @param toClass 映射类
* @param data 数据(集合)
* @param configMap 自定义配置
* @return 映射类对象
*/
/* public <E, T> List<E> mapAsList(Class<E> toClass, Collection<T> data, Map<String, String> configMap) {
T t = data.stream().findFirst().orElseThrow(() -> new ResourceNotExistException("映射集合,数据集合为空"));
MapperFacade mapperFacade = this.getMapperFacade(toClass, t.getClass(), configMap);
return mapperFacade.mapAsList(data, toClass);
}*/
/**
* 获取自定义映射
*
* @param toClass 映射类
* @param dataClass 数据映射类
* @param configMap 自定义配置
* @return 映射类对象
*/
private <E, T> MapperFacade getMapperFacade(Class<E> toClass, Class<T> dataClass, Map<String, String> configMap) {
String mapKey = dataClass.getCanonicalName() + "_" + toClass.getCanonicalName();
MapperFacade mapperFacade = CACHE_MAPPER_FACADE_MAP.get(mapKey);
if (Objects.isNull(mapperFacade)) {
MapperFactory factory = new DefaultMapperFactory.Builder().build();
ClassMapBuilder classMapBuilder = factory.classMap(dataClass, toClass);
configMap.forEach(classMapBuilder::field);
classMapBuilder.byDefault().register();
mapperFacade = factory.getMapperFacade();
CACHE_MAPPER_FACADE_MAP.put(mapKey, mapperFacade);
}
return mapperFacade;
}
}