几种Bean映射工具介绍

一、介绍

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一样
  • 其他一些组件包主要基于 AOPASMCGlib,的技术手段实现的,所以也会有相应的性能损耗

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✨✨

官网文档地址:https://github.com/mapstruct/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

https://blog.csdn.net/u011622109/article/details/106910538

https://blog.csdn.net/scholar_man/article/details/115917932

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值