Bean的拷贝工具对比

1.前言

在进行分层和微服务模式下的开发时,我们经常回遇到DTO、DO、VO等之间的转换,如果手动对各个属性进行set的话容易漏掉,同时碰到字段特别多的情况会让人写到吐,当我们对字段进行增减的时候还需要手动处理。为了避免出现这些臃肿又容易出错的代码,我们一般都会使用Bean的拷贝工具。下面来介绍常见的几种。

2.Apache BeanUtils

org.apache.commons.beanutils.BeanUtils.copyProperties

这个Apache下最早使用的Bean拷贝工具类,将我们从一坨Set中解放了出来,代码非常优雅。在Maven中引入

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

将user的代码赋值给userDTO:

UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(userDTO,user);

不过它有个致命的缺点:慢。

3.Spring BeanUtils

org.springframework.beans.BeanUtils.copyProperties

这个工具类在spring-beans包中,只要使用的Spring开发,直接使用即可。使用方式跟上面类似,只不过source和target位置正好相反,也就是如下,将user的代码赋值给userDTO:

UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user,userDTO);

Spring的BeanUtils与Apache的BeanUtils原理都是用的反射来进行赋值,只不过,在内部实现的时候,Spring少了很多检查,所以速度要比Apache的快很多。

4.BeanCopier

net.sf.cglib.beans.BeanCopier

BeanCopier基于CGLib动态代理,在运行期间会生成代理对象(此处是有反射的),但在对属性进行Copy的时候没有使用反射,正式因为这一点使其速度比纯使用反射快了一些。CGLib包在Spring内部已经集成了,不需要额外的添加。当然,如果没用Spring,可以添加如下引用:

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

使用的时候,为了使用的便捷性,同时为了防止每次都生成代理类,将第一次生成的BeanCopier缓存起来,我们需要编写一个工具类:

public class BeanCopierUtils {

    private static Logger logger = LoggerFactory.getLogger(BeanCopierUtils.class);
    /**
     * BeanCopier缓存,flyweight模式的运用,将BeanCopier缓存起来。
     */
    private static Map<String, BeanCopier> beanCopierCacheMap = new HashMap<String, BeanCopier>();
      
    /**
     * 将source对象的属性拷贝到target对象中去
     * @param source source对象
     * @param target target对象
     */
    public static void copyProperties(Object source, Object target){  
        String cacheKey = source.getClass().toString() + 
                target.getClass().toString();  
        
        BeanCopier beanCopier = null;  
        
        if (!beanCopierCacheMap.containsKey(cacheKey)) {  
            synchronized(BeanCopierUtils.class) {
                 if (!beanCopierCacheMap.containsKey(cacheKey)) {  
                     beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);  
                     beanCopierCacheMap.put(cacheKey, beanCopier);  
                 }
            }
        } else {  
            beanCopier = beanCopierCacheMap.get(cacheKey);   
        }  
        
        beanCopier.copy(source, target, null);
    }

    /**
     * 更优雅的属性复制方法
     * @param source 数据源对象
     * @param clazz 目标Class
     * @return 克隆后的对象
     */
    public static <T> T copyProperties(Object source,Class<T> clazz) {
        T target = null;
        try {
            target = clazz.newInstance();
            BeanCopierUtils.copyProperties(source, target);
        } catch (Exception e) {
            logger.error("error", e);
        }
        return target;
    }
  
}  

将user的数据赋值给UserDTO,使用方式如下:

BeanCopierUtils.copyProperties(user,UserDTO.class)

5.MapStruct

上面的几种工具都是基于Java的运行期进行处理的,MapStruct另辟蹊径,在编译期动起了心思。官方文档:https://mapstruct.org/documentation/stable/reference/html/ 。
添加引用:

<properties>
    <org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>

在maven的编译插件下需要添加如下配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${org.mapstruct.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

由于MapStruct是依赖的Getter、Setter方法的,如果项目中使用了Lombok,还需要在上面的annotationProcessorPaths标签下添加配置:

<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
</path>

创建一个映射工具类:

@Mapper
public interface PoToDtoMapping {
    /**
     * 转换器实例
     */
    PoToDtoMapping INSTANCE = Mappers.getMapper(PoToDtoMapping.class);
    /**
     * 将User转换为UserDTO
     * @param user
     * @return
     */
    UserDTO copyUserToUserDTO(User user);
}

当我们编译完项目之后,可以在 target → generated-sources → annotations目录下找到MapStruct帮我们生成了一个上面的接口实现类:PoToDtoMappingImpl.java,里面的内容如下:

public class PoToDtoMappingImpl implements PoToDtoMapping {

    @Override
    public UserDTO copyUserToUserDTO(User user) {
        if ( user == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setId( user.getId() );
        userDTO.setName( user.getName() );
        userDTO.setSex( user.getSex() );
        userDTO.setAddress( user.getAddress() );

        return userDTO;
    }
}

看,实际上就是我们手动set的方式,只不过代码不用我们自己写了。当然这只是MapStruct最为基本的使用方法,它还有很多自定义的映射方式,具体可以阅读官方文档。

将user属性的数据赋值给userDTO,使用方式如下:

PoToDtoMapping.INSTANCE.copyUserToUserDTO(user)

6.对比

6.1 代码样例

我是使用的SpringBoot进行的测试,直接写在了Controller里,示例代码如下:

public class TestController {
    private static List<User> users = new ArrayList<>();
    static {
        for (int i = 0; i < 1000000; i++) {
            User user = new User();
            user.setId(i+1);
            user.setName("wlc"+i);
            user.setAddress("爪哇"+i);
            users.add(user);
        }
    }

    @GetMapping("/bean-copy")
    public void beanCopyTest(){

        //结论:从性能上来说:
        //      mapStruct >>  BeanCopier > Spring BeanUtils >> Apache BeanUtils
        //     从使用编辑性上来说,BeanCopier比较优雅

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<UserDTO> userDTOS1 = new ArrayList<>();
        for (User user : users) {
            userDTOS1.add(BeanCopierUtils.copyProperties(user,UserDTO.class));
        }
        stopWatch.stop();
        log.info("BeanCopier花费时间:{}ms",stopWatch.getLastTaskTimeMillis());
        stopWatch.start();
        List<UserDTO> userDTOS2 = new ArrayList<>();
        for (User user : users) {
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(user,userDTO);
            userDTOS2.add(userDTO);
        }
        stopWatch.stop();
        log.info("Spring BeanUtils花费时间:{}ms",stopWatch.getLastTaskTimeMillis());

        stopWatch.start();
        List<UserDTO> userDTOS3 = new ArrayList<>();
        for (User user : users) {
            userDTOS3.add(PoToDtoMapping.INSTANCE.copyUserToUserDTO(user));
        }
        stopWatch.stop();
        log.info("MapStruct 拷贝花费时间:{}ms",stopWatch.getLastTaskTimeMillis());

        stopWatch.start();
        List<UserDTO> userDTOS4 = new ArrayList<>();
        try{
            for (User user : users) {
                UserDTO userDTO = new UserDTO();
                org.apache.commons.beanutils.BeanUtils.copyProperties(userDTO,user);
                userDTOS4.add(userDTO);
            }
        }catch (Exception e){
            log.error("拷贝失败:{}",e);
        }
        stopWatch.stop();
        log.info("Apache BeanUtils花费时间:{}ms",stopWatch.getLastTaskTimeMillis());
        log.info("总共花费时间:{}ms",stopWatch.getTotalTimeMillis());

    }
}

结果如下:

 BeanCopier花费时间:715ms
 Spring BeanUtils花费时间:1473ms
 MapStruct 拷贝花费时间:34ms
 Apache BeanUtils花费时间:10313ms
 总共花费时间:12536ms

 BeanCopier花费时间:637ms
 Spring BeanUtils花费时间:1553ms
 MapStruct 拷贝花费时间:33ms
 Apache BeanUtils花费时间:10136ms
 总共花费时间:12361ms

 BeanCopier花费时间:624ms
 Spring BeanUtils花费时间:1485ms
 MapStruct 拷贝花费时间:85ms
 Apache BeanUtils花费时间:9857ms
 总共花费时间:12053ms

6.2 结论

从上面的结果可以看出,速度对比:最快的工具MapStruct当仁不让,是最慢的Apache BeanUtils速度的100倍以上,BeanCopier速度次之,大约是Spring BeanUtils的2倍。
使用便捷性对比: MapStruct使用相对来说更不方便,但由于其功能比较灵活,可以定制很多Bean的拷贝方式,基本上能杜绝Set;其他三种使用更为便捷。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值