屬性拷貝你還在用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

這期差不多就這些了.如果對你有用的話,歡迎評論,交流,關注,點贊.

謝謝大家啦~


分享到:


相關文章: