常见的Bean映射工具

在工作中,经常会需要将对象转换成不同的形式来适应不同的api,例如对接第三方的接口,将数据落盘到自己的数据库中;在比如不通分层之间的对象转换,从业务层到表现层,有些字段是不需要展示的等。
进行这种转换,除了编写大量的 get/set 代码,像spring、apache也提供了很多的工具类可以实现。

  1. 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>
    
  2. 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();
            }
    }
    
  3. 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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值