文章目录
一、介绍
1、功能介绍
在 Java 系统工程开发过程中,都会有各个层之间的对象转换,比如 VO、DTO、PO、VO 等,而如果都是手动get、set
又太浪费时间,还可能操作错误,所以选择一个自动化工具会更加方便;同时在工作也常常遇到将java的Bean对象转化为Map,或者将Map转为Bean对象的情况。
2、Bean互转不同方法与性能对比
目前用于对象属性转换有12种,包括普通的get/set、json2Json、Apache属性拷贝、Spring属性拷贝、bean-mapping、bean-mapping-asm、BeanCopier、Orika、Dozer、ModelMapper、JMapper、MapStruct
- Spring 提供的
BeanUtils.copyProperties
是大家代码里最常出现的工具类,注意不是Apache
包下 - 手动
get、set
性能是最好的,另外MapStruct
性能和操作也很方便,因为它本身就是在编译期生成get、set
代码,和我们写get、set
一样 - 其他一些组件包主要基于
AOP
、ASM
、CGlib
,的技术手段实现的,所以也会有相应的性能损耗
3、Bean/Map互转
工作常常遇到将java的Bean对象转化为Map,或者将Map转为Bean对象,常见手段有以下几种
- 通过json工具,将Bean转json,再将json转Map(效率低)
- jdk的反射,获取类的属性,进行转化(比较麻烦,书写代码比较多)
- 通过工具类
BeanMap
来完成(效率高,底层也是基于反射,不过做了些优化,比如缓存等手段 ,推荐) - 通过
Apache的BeanUtils
来完成(bean转化map是Map<String, String>类型)
二、Bean转换案例
1、源VO和目标VO
//源类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class SourceVO {
private String name;
private String password;
private Integer age;
}
//目标类
@Data
@Accessors(chain = true)
public class TargetVO {
private String name;
private String password;
private Integer age;
}
2、get/set
这种方式也是日常使用的最多的,性能十分优秀,但是操作起来有点麻烦。多个get/set可以通过 Shift+Alt 选中所有属性,Shift+Tab 归并到一列,接下来在使用 Alt 选中这一列,批量操作粘贴 targetVO.set
以及快捷键大写属性首字母,最后切换到结尾补充括号和分号,最终格式化一下就可以了
3、Json2Json
把对象转JSON串,再把JSON转另外一个对象,这种方式性能不是很高
/**
* 这里用了fastjson
* 两个对象或集合同名属性赋值
*/
public class ObjectConversion {
/**
* 从List<A> copy到List<B>
* @param list List<B>
* @param clazz B
* @return List<B>
*/
public static <T> List<T> copy(List<?> list,Class<T> clazz){
String oldOb = JSON.toJSONString(list);
return JSON.parseArray(oldOb, clazz);
}
/**
* 从对象A copy到 对象B
* @param ob A
* @param clazz B.class
* @return B
*/
public static <T> T copy(Object ob, Class<T> clazz){
String oldOb = JSON.toJSONString(ob);
return JSON.parseObject(oldOb, clazz);
}
}
public class Test {
public static void main(String[] args){
SourceVO sourceVO = new SourceVO("shawn","123456",18);
TargetVO targetVO = ObjectConversion.copy(sourceVO, TargetVO.class);
System.out.println(targetVO);
}
}
4、Spring copyProperties✨
这个方法是反射的属性拷贝,Spring 提供的 copyProperties 要比 Apache 好用的多,性能和操作都比较好,这个包是org.springframework.beans.BeanUtils
另外这里考虑到属性不同的字段的拷贝,还额外创建了一个回调函数进行映射处理
//回调函数定义,这里用了java8特性函数式接口
@FunctionalInterface
public interface BeanCopyUtilCallBack <S, T> {
/**
* 定义默认回调方法
* @param t
* @param s
*/
void callBack(S t, T s);
}
/**
* 定义拷贝工具类
*/
public class BeanCopyUtil extends BeanUtils {
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier) {
return convertTo(source, targetSupplier, null);
}
/**
* 转换对象
*
* @param source 源对象
* @param targetSupplier 目标对象供应方
* @param callBack 回调方法
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier, ConvertCallBack<S, T> callBack) {
if (null == source || null == targetSupplier) {
return null;
}
T target = targetSupplier.get();
copyProperties(source, target);
if (callBack != null) {
callBack.callBack(source, target);
}
return target;
}
/**
* 集合数据的拷贝
* @param sources: 数据源类
* @param target: 目标类::new(eg: UserVO::new)
* @return
*/
public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target) {
return copyListProperties(sources, target, null);
}
/**
* 带回调函数的集合数据的拷贝(可自定义字段拷贝规则)
* @param sources: 数据源类
* @param target: 目标类::new(eg: UserVO::new)
* @param callBack: 回调函数
* @return
*/
public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> target, BeanCopyUtilCallBack<S, T> callBack) {
List<T> list = new ArrayList<>(sources.size());
for (S source : sources) {
T t = target.get();
copyProperties(source, t);
list.add(t);
if (callBack != null) {
// 回调
callBack.callBack(source, t);
}
}
return list;
}
}
这里的回调函数可以使用Enum枚举进行规范化,最终进行测试,成功拷贝
ublic class Test {
public static void main(String[] args){
// 单个拷贝
SourceVO sourceBean = new SourceVO("shawn", "123456", 18);
TargetVO targetBean = new TargetVO();
BeanUtils.copyProperties(sourceBean,targetBean);
System.out.println(targetBean);
// 列表拷贝
List<SourceVO> sourceVOList = new ArrayList<>();
sourceVOList.add(new SourceVO("shawn","123456",18));
sourceVOList.add(new SourceVO("shawn1","12345678",20));
List<TargetVO> targetVOList= BeanCopyUtil.copyListProperties(sourceVOList, TargetVO::new,
(sourceVO,targetVO)->{
targetVO.setAge(sourceVO.getAge());
});
System.out.println(targetVOList);
}
}
另一种方法进行转换,常用
public class JPAUtils {
//TODO list<Entity> 集合对象转list<Vo> (Stream 方式)
public static <T, V> List<V> listVoStream(List<T> oldList, Class<V> v) {
List<V> voList = oldList.stream().map(item -> {
try {
return (V) BeanDtoVoUtils.convert(item, v.newInstance().getClass());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList());
return voList;
}
}
public class BeanDtoVoUtils<V, E> {
/**
* TODO dot ,Do ,entity 相互转换
* 同:BeanUtils.copyProperties(dtoEntity, newInstance);
*
* @param oldClass 原数据--Dto,Vo,entity
* @param newClass 转换为--Dto,Vo,entity
* @return
*/
public static <E> E convert(Object oldClass, Class<E> newClass) {
// 判断oldClass 是否为空!
if (oldClass == null) {
return null;
}
// 判断newClass 是否为空
if (newClass == null) {
return null;
}
try {
// 创建新的对象实例
E newInstance = newClass.newInstance();
// 把原对象数据拷贝到新的对象
BeanUtils.copyProperties(oldClass, newInstance);
// 返回新对象
return newInstance;
} catch (Exception e) {
return null;
}
}
}
5、BeanCopier
Cglib BeanCopier 的原理与Beanutils 原理不太一样,其主要使用 字节码技术动态生成一个代理类,代理类实现get 和 set方法。生成代理类过程存在一定开销,但是一旦生成,我们可以缓存起来重复使用,所有 Cglib 性能相比Beanutils 性能比较好整体性能不错,使用也不复杂
//工具类
public class WrapperBeanCopier {
/**
* 单个对象属性拷贝
* @param source 源对象
* @param clazz 目标对象Class
* @param <T> 目标对象类型
* @param <M> 源对象类型
* @return 目标对象
*/
public static <T, M> T copyProperties(M source, Class<T> clazz){
if (Objects.isNull(source) || Objects.isNull(clazz))
throw new IllegalArgumentException();
return copyProperties(source, clazz, null);
}
/**
* 列表对象拷贝
* @param sources 源列表
* @param clazz 源列表对象Class
* @param <T> 目标列表对象类型
* @param <M> 源列表对象类型
* @return 目标列表
*/
public static <T, M> List<T> copyObjects(List<M> sources, Class<T> clazz) {
if (Objects.isNull(sources) || Objects.isNull(clazz) || sources.isEmpty())
throw new IllegalArgumentException();
BeanCopier copier = BeanCopier.create(sources.get(0).getClass(), clazz, false);
return Optional.of(sources)
.orElse(new ArrayList<>())
.stream().map(m -> copyProperties(m, clazz, copier))
.collect(Collectors.toList());
}
/**
* 单个对象属性拷贝
* @param source 源对象
* @param clazz 目标对象Class
* @param copier copier
* @param <T> 目标对象类型
* @param <M> 源对象类型
* @return 目标对象
*/
private static <T, M> T copyProperties(M source, Class<T> clazz, BeanCopier copier){
if (null == copier){
copier = BeanCopier.create(source.getClass(), clazz, false);
}
T t = null;
try {
t = clazz.newInstance();
copier.copy(source, t, null);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
}
public class Test {
public static void main(String[] args){
// 单个拷贝
SourceVO sourceVO = new SourceVO("shawn", "123456", 18);
TargetVO targetVO = WrapperBeanCopier.copyProperties(sourceVO, TargetVO.class);
System.out.println(targetVO);
// 列表拷贝
List<SourceVO> sourceVOList = new ArrayList<>();
sourceVOList.add(new SourceVO("shawn","123456",18));
sourceVOList.add(new SourceVO("shawn1","12345678",20));
List<TargetVO> targetVOS = WrapperBeanCopier.copyObjects(sourceVOList, TargetVO.class);
System.out.println(targetVOS);
}
}
如果要自定义转换,可以使用new Converter()
,一旦我们自己打开使用转换器,所有属性复制都需要我们自己来了,否则会导致无法复制。另外这里需要注意的是拷贝对象要去除@Accessors(chain = true)
注解,因为该注解会将 setter
方法的返回值由 void
修改为当前对象。这导致 setter
的方法签名改变,最终导致 BeanCopier
无法识别现有的 setter
方法
6、MapStruct✨✨
MapStruct是一款基于Java注解的对象属性映射工具,在Github上已经有4.5K+Star。使用的时候我们只要在接口中定义好对象属性映射规则,它就能自动生成映射实现类,不使用反射,性能优秀,能实现各种复杂映射。(强烈推荐)
首先导入Maven依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
<scope>compile</scope>
</dependency>
创建Bean对象
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class SourceVO {
private String name;
private String password;
private Integer age;
private Date birthday;
}
/*---------------------*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TargetVO {
private String name;
private String pwd;
private Integer age;
private String birthday;
}
编写Mapper文件,实现同名同类型属性、不同名称属性、不同类型属性的映射
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "pwd", source = "password")
@Mapping(source = "birthday",target = "birthday", dateFormat = "yyyy-MM-dd")
TargetVO getTargetVO(SourceVO sourceVO);
}
最后直接通过接口中的INSTANCE
实例调用转换方法
public class Test {
public static void main(String[] args){
SourceVO sourceVO = new SourceVO("shawn", "123456", 18, new Date());
TargetVO targetVO = UserMapper.INSTANCE.getTargetVO(sourceVO);
System.out.println(targetVO);
}
}
其实MapStruct的实现原理很简单,就是根据我们在Mapper接口中使用的@Mapper
和@Mapping
等注解,在运行时生成接口的实现类,我们可以打开项目的target
目录查看
初次之外,MapStruct使用有:
- 基本映射、集合映射、子对象映射、合并映射
- 使用依赖注入、使用常量/默认值/表达式、映射前后自定义、处理映射异常等
详情可参考:
https://blog.csdn.net/zhenghongcs/article/details/121349361
https://mapstruct.org/documentation/stable/reference/html/
三、Bean/Map互转
1、JSON 工具互转
public class BeanMapUtilByJson {
/**
* 对象转Map
*
* @param object
* @return
*/
public static Map beanToMap(Object object) {
return JSONObject.parseObject(JSON.toJSONString(object), Map.class);
}
/**
* map转对象
*
* @param map
* @param beanClass
* @param <T>
* @return
*/
public static <T> T mapToBean(Map map, Class<T> beanClass) {
return JSONObject.parseObject(JSON.toJSONString(map), beanClass);
}
}
2、JDK反射互转
这种操作是利用 java 原生提供的反射特性来实现互转
public class BeanMapUtilByReflect {
/**
* 对象转Map
*
* @param object
* @return
* @throws IllegalAccessException
*/
public static Map beanToMap(Object object) throws IllegalAccessException {
Map<String, Object> map = new HashMap<String, Object>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
map.put(field.getName(), field.get(object));
}
return map;
}
/**
* map转对象
*
* @param map
* @param beanClass
* @param <T>
* @return
* @throws Exception
*/
public static<T> T mapToBean(Map map, Class<T> beanClass) throws Exception {
T object = beanClass.getDeclaredConstructor().newInstance();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
int mod = field.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
field.setAccessible(true);
if (map.containsKey(field.getName())) {
field.set(object, map.get(field.getName()));
}
}
return object;
}
}
3、BeanMap转换(推荐)
BeanMap是org.springframework.cglib.beans.BeanMap,在springcore中
public class BeanMapTool {
public static <T> Map<String, ?> beanToMap(T bean) {
BeanMap beanMap = BeanMap.create(bean);
Map<String, Object> map = new HashMap<>();
beanMap.forEach((key, value) -> {
map.put(String.valueOf(key), value);
});
return map;
}
public static <T> T mapToBean(Map<String, ?> map, Class<T> clazz)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
T bean = clazz.getDeclaredConstructor().newInstance();
BeanMap beanMap = BeanMap.create(bean);
beanMap.putAll(map);
return bean;
}
public static <T> List<Map<String, ?>> objectsToMaps(List<T> objList) {
List<Map<String, ?>> list = new ArrayList<>();
if (objList != null && objList.size() > 0) {
Map<String, ?> map = null;
T bean = null;
for (int i = 0, size = objList.size(); i < size; i++) {
bean = objList.get(i);
map = beanToMap(bean);
list.add(map);
}
}
return list;
}
public static <T> List<T> mapsToObjects(List<Map<String, ?>> maps, Class<T> clazz)
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
List<T> list = new ArrayList<>();
if (maps != null && maps.size() > 0) {
Map<String, ?> map = null;
for (int i = 0, size = maps.size(); i < size; i++) {
map = maps.get(i);
T bean = mapToBean(map, clazz);
list.add(bean);
}
}
return list;
}
}
4、BeanUtils互转
首先导入依赖
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
工具类
public class BeanUtilsTool {
public static <T> Map<String, String> beanToMap(T bean)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
return BeanUtils.describe(bean);
}
public static <T> T mapToBean(Map<String, Object> map, Class<T> bean)
throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
T t = bean.getDeclaredConstructor().newInstance();
BeanUtils.populate(t, map);
return t;
}
}
5、实战技巧
注意反射方法进行的时候一定要有无参构造,否则会报错找不到该方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Bean {
private String name;
private int age;
}
// 举例,工具类自己替换
public class Test {
public static void main(String[] args) throws Exception {
Bean shawn = new Bean("shawn", 18);
Map map = BeanMapUtilByReflect.beanToMap(shawn);
System.out.println(map);
System.out.println(BeanMapUtilByReflect.mapToBean(map,Bean.class));
}
}
参考文章
https://mp.weixin.qq.com/s/_QJa5RSxvPBsqXo8yS5-pg