属性拷贝你还在用BeanUtils?

属性拷贝你还在用BeanUtils?

从PO, DTO到Domain Driven Design这篇文章提到各种实体类, 工作中我们往往因为领域的问题要在DO,BO,VO,DTO之间来回转换.

最初

年轻时候的我是这样做的.

属性拷贝你还在用BeanUtils?

可以看出我这套块编辑的操作还是挺骚的. 但还是感觉麻烦.

于是我找了几个常用的三方工具

  • org.apache.commons.beanutils.BeanUtils.copyProperties
  • org.apache.commons.beanutils.PropertyUtils.copyProperties
  • org.springframework.beans.BeanUtils.copyProperties
  • org.springframework.cglib.beans.BeanCopier.copy
  • org.mapstruct

本文主推使用mapstruct做属性复制,请看下文

通常选择一个工具我们要从性能和实用性方面两个角度去考量.

性能检测

为了足够硬核,我们实践检验效果.通过一组测试来检查5个方法的性能表现.

测试代码如下:

<code>@Slf4j
public class CopyDemoTest {

   public UserMainBO bo;

   public static int count = 1000000;

   @Before
   public void init(){
       bo = new UserMainBO();
       bo.setId(1L);
  }

   @Test
   public void mapstruct() {
       UserMainVOMapping INSTANCE = Mappers.getMapper( UserMainVOMapping.class );
       log.info("star------------");
       for (int i = 1; i <=count; i++) {
           // log.debug(i+"");
           UserMainVO vo = INSTANCE.toVO(bo);
      }
       log.info("end------------");
  }

   @Test
   public void beanCopier() {
       log.info("star------------");
       BeanCopier copier = BeanCopier.create(UserMainBO.class, UserMainVO.class, false);
       for (int i = 1; i <=count; i++) {
           // log.debug(i+"");
           UserMainVO vo = new UserMainVO();
           copier.copy(bo, vo, null);
      }
       log.info("end------------");
  }

   @Test
   public void springBeanUtils(){
       log.info("star------------");
       for (int i = 1; i <=count; i++) {
           // log.debug(i+"");
           UserMainVO vo = new UserMainVO();
           BeanUtils.copyProperties(bo, vo);
      }
       log.info("end------------");
  }

   @Test
   public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException {
       for (int i = 1; i <=count; i++) {

           // log.debug(i+"");
           UserMainVO vo = new UserMainVO();
           org.apache.commons.beanutils.BeanUtils.copyProperties(bo, vo);
      }
       log.info("end------------");
  }

   @Test
   public void apachePropertyUtils() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
       log.info("star------------");
       for (int i = 1; i <=count; i++) {
           // log.debug(i+"");
           UserMainVO vo = new UserMainVO();
           PropertyUtils.copyProperties(bo, vo);
      }
       log.info("end------------");
  }

}/<code>


属性拷贝你还在用BeanUtils?


属性拷贝你还在用BeanUtils?

属性拷贝你还在用BeanUtils?

属性拷贝你还在用BeanUtils?

如图所示, 可看出其性能一次是:

mapstruct > BeanCopier > spring.BeanUtils > apache.PropertyUtils > apache.BeanUtils

mapstruct 相当的顶, 性能遥遥领先.

分析

我们看下源码

apache.BeanUtils

<code>while(var13.hasNext()) {
   Entry<string> entry = (Entry)var13.next();
   String name = (String)entry.getKey();
   if (this.getPropertyUtils().isWriteable(dest, name)) {

      // 核心逻辑
       this.copyProperty(dest, name, entry.getValue());
  }
}/<string>/<code>

apache.PropertyUtils

<code>value = this.getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
  ((DynaBean)dest).set(name, value);
} else {
// 核心逻辑
  this.setSimpleProperty(dest, name, value);
}/<code>

Apache 主要集中了各种丰富的功能(日志、转换、解析等等),导致性能变差。

而Spring BeanUtils则是直接通过反射来读取和写入

<code>for(int var9 = 0; var9 < var8; ++var9) {
   PropertyDescriptor targetPd = var7[var9];
   Method writeMethod = targetPd.getWriteMethod();
   if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
       PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
       if (sourcePd != null) {
           Method readMethod = sourcePd.getReadMethod();
           if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
               try {
                   if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                       readMethod.setAccessible(true);
                  }

                   Object value = readMethod.invoke(source);
                   if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                       writeMethod.setAccessible(true);
                  }

                   writeMethod.invoke(target, value);
              } catch (Throwable var15) {
                   throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
              }
          }
      }
  }
}/<code>

BeanCopier

<code>public abstract void copy(Object var1, Object var2, Converter var3);/<code>

BeanCopier动态生成一个要代理类的子类,其实就是通过字节码方式转换成性能最好的get和set方式,只需考虑创建BeanCopier的开销.

mapstruct

这里我们打开UserMainVOMapping的实现类可以看出,其相当硬核.直接用maven 在编译期间生产对应的实现类.

属性拷贝你还在用BeanUtils?

看到这里有的观众老爷就会说了:"那这个性能差的也不多啊, 我为什么要用mapsturct?" 请接着往下看

实用性

mapstruct的接口

<code>@Mapper(componentModel = "spring")
public interface UserMainVOMapping {

   @Mappings({
           @Mapping(source = "testB", target = "testV")
  })
   UserMainVO toVO(UserMainBO userMainBO);

   List<usermainvo> toVOList(List<usermainbo> list);

   PageInfo<usermainbo> toVOPage(PageInfo<usermainbo> page);
}/<usermainbo>/<usermainbo>/<usermainbo>/<usermainvo>/<code>

这里我们可以看出一下几个特征

  • 直接定义接口就可以实现属性复制
  • 属性对应可以 n source -> 1tager
  • 集合形式也可以转
  • 特殊实体类只要属性相同也可以转
  • 可以通过@Mapping 指定属性复制路径

足见其功能的强大

mapstruct的使用

maven 配置

这里只介绍 maven形式配置, gradle 和 ant 请参考官网

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

<dependencies>
       <dependency>
           <groupid>org.mapstruct/<groupid>
           <artifactid>mapstruct-processor/<artifactid>
           <version>${org.mapstruct.version}/<version>
           <scope>provided/<scope>
       /<dependency>
/<dependencies>

<build>
   <plugins>
       <plugin>
           <groupid>org.apache.maven.plugins/<groupid>
           <artifactid>maven-compiler-plugin/<artifactid>
           <configuration>
               <annotationprocessorpaths>
                   <path>
                       <groupid>org.projectlombok/<groupid>
                       <artifactid>lombok/<artifactid>
                       <version>${lombok.version}/<version>
                   /<path>
                   <path>
                       <groupid>org.mapstruct/<groupid>
                       <artifactid>mapstruct-processor/<artifactid>
                       <version>${org.mapstruct.version}/<version>
                   /<path>
               /<annotationprocessorpaths>
           /<configuration>
       /<plugin>
   /<plugins>
/<build>/<code>

注意这里配置一下lombok, 否则启动会冲突

Mapping 接口编写

<code>@Mapper(componentModel = "spring")
public interface UserMainVOMapping {

   @Mappings({
           @Mapping(source = "testB", target = "testV")
  })
   UserMainVO toVO(UserMainBO userMainBO);

   List<usermainvo> toVOList(List<usermainbo> list);

   PageInfo<usermainbo> toVOPage(PageInfo<usermainbo> page);
}/<usermainbo>/<usermainbo>/<usermainbo>/<usermainvo>/<code>

使用类示意

<code>@RestController
public class UserController {

   @Resource
   private UserMainVOMapping userMainVOMapping;
   @Resource
   private UserMainDOService userMainDOService;

   @GetMapping("/userMain/page")
   public ResultVO page(int page, int pageSize, String nickname)   {
       PageInfo<usermainbo> userPage = userMainDOService.page(page, pageSize, nickname, false);
       UserMainVOMapping INSTANCE = Mappers.getMapper( UserMainVOMapping.class);
       return new ResultVO(ResultEnum.SUCCESS, INSTANCE.toVOPage(userPage));
  }

   @GetMapping("/userMain/page2")
   public ResultVO page2(int page, int pageSize, String nickname)   {
       PageInfo<usermainbo> userPage = userMainDOService.page(page, pageSize, nickname, false);
       return new ResultVO(ResultEnum.SUCCESS, userMainVOMapping.toVOPage(userPage));
  }

   @GetMapping("moreToOne")
   public UserMainVO moreToOne(){
       UserMainBO userMainBO = new UserMainBO();
       userMainBO.setId(1L);
       userMainBO.setTestB("test");
       SubBO subBO = new SubBO();
       subBO.setA("A");
       userMainBO.setSub(subBO);
       return userMainVOMapping.toVO(userMainBO);
  }
}/<usermainbo>/<usermainbo>/<code>

注意

可以在Mapping类上加@Mapper(componentModel = "spring"), 通过注入的方式引入Mapper接口

可以通过Mappers.getMapper( UserMainVOMapping.class) 的方式获取Mapper接口

总结

  1. 众多属性拷贝工具中mapstruct相对比较好
  2. mapstruct书写性能极高
  3. mapstruct书写非常方便
  4. mapstruct非常灵活可用来定义各种类型的属性copy

这期差不多就这些了.如果对你有用的话,欢迎评论,交流,关注,点赞.

谢谢大家啦~


分享到:


相關文章: