屬性拷貝你還在用BeanUtils?

屬性拷貝你還在用BeanUtils?

從PO, DTO到Domain Driven Design這篇文章提到各類實體類, 工做中咱們每每由於領域的問題要在DO,BO,VO,DTO之間來回轉換.

最初

年輕時候的我是這樣作的.html

塊編輯

能夠看出我這套塊編輯的操做仍是挺騷的. 但仍是感受麻煩.java

因而我找了幾個經常使用的三方工具spring

  • 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作屬性複製,請看下文

一般選擇一個工具咱們要從性能和實用性方面兩個角度去考量.apache

性能檢測

爲了足夠硬核,咱們實踐檢驗效果.經過一組測試來檢查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

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

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

mapstruct 至關的頂, 性能遙遙領先.maven

分析

咱們看下源碼ide

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

注意這裏配置一下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

這期差很少就這些了.若是對你有用的話,歡迎評論,交流,關注,點贊.

謝謝你們啦~

相關文章
相關標籤/搜索