屬性拷貝你還在用BeanUtils?


屬性拷貝你還在用BeanUtils?

從PO, DTO到Domain Driven Design這篇文章提到各種實體類, 工作中我們往往因為領域的問題要在DO,BO,VO,DTO之間來回轉換.

最初

年輕時候的我是這樣做的.

塊編輯

可以看出我這套塊編輯的操作還是挺騷的. 但還是感覺麻煩.

於是我找了幾個常用的三方工具

  • 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個方法的性能表現.

測試代碼如下:

@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------------");
    }

}
tools/count 1000/次 10000/次 100000/次 1000000/次
apache.BeanUtils 550ms 1085ms 4287ms 32088ms
apache.PropertyUtils 232ms 330ms 2080ms 20681ms
cglib.BeanCopier 73ms 106ms 102ms 99ms
mapstruct 91ms 5ms 7ms 12ms
spring.BeanUtils 5ms 188ms 336ms 844ms

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

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

mapstruct 相當的頂, 性能遙遙領先.

分析

我們看下源碼

apache.BeanUtils

while(var13.hasNext()) {
    Entry<String, Object> entry = (Entry)var13.next();
    String name = (String)entry.getKey();
    if (this.getPropertyUtils().isWriteable(dest, name)) {
       // 核心邏輯
        this.copyProperty(dest, name, entry.getValue());
    }
}

apache.PropertyUtils

value = this.getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
    ((DynaBean)dest).set(name, value);
} else {
	// 核心邏輯
    this.setSimpleProperty(dest, name, value);
}

Apache 主要集中了各種豐富的功能(日志、轉換、解析等等),導致性能變差。

而Spring BeanUtils則是直接通過反射來讀取和寫入

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);
                }
            }
        }
    }
}

BeanCopier

public abstract void copy(Object var1, Object var2, Converter var3);

BeanCopier動態生成一個要代理類的子類,其實就是通過字節碼方式轉換成性能最好的get和set方式,只需考慮創建BeanCopier的開銷.

mapstruct

這里我們打開UserMainVOMapping的實現類可以看出,其相當硬核.直接用maven 在編譯期間生產對應的實現類.

3059vn.gif

看到這里有的觀眾老爺就會說了:"那這個性能差的也不多啊, 我為什么要用mapsturct?" 請接着往下看

實用性

mapstruct的接口

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

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

    List<UserMainVO> toVOList(List<UserMainBO> list);

    PageInfo<UserMainVO> toVOPage(PageInfo<UserMainBO> page);
}

這里我們可以看出一下幾個特征

  • 直接定義接口就可以實現屬性復制
  • 屬性對應可以 n source -> 1target
  • 集合形式也可以轉
  • 特殊實體類只要屬性相同也可以轉
  • 可以通過@Mapping 指定屬性復制路徑

足見其功能的強大

mapstruct的使用

maven 配置

這里只介紹 maven形式配置, gradle 和 ant 請參考官網


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

注意這里配置一下lombok, 否則啟動會沖突

Mapping 接口編寫

@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);
}

使用類示意

@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);
    }
}

注意

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

  2. 可以通過Mappers.getMapper( UserMainVOMapping.class) 的方式獲取Mapper接口

總結

  1. 眾多屬性拷貝工具中mapstruct相對比較好
  2. mapstruct書寫性能極高
  3. mapstruct書寫非常方便
  4. mapstruct非常靈活可用來定義各種類型的屬性copy

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

謝謝大家啦~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM