java 性能最好的对象、列表深拷贝框架MapStruct(一秒拷贝百万次)

java 性能最好的对象、列表深拷贝框架MapStruct

MapStruct是一款非常实用Java工具,主要用于解决对象之间的拷贝问题,比如PO/DTO/VO/QueryParam之间的转换问题。区别于BeanUtils这种通过反射,它通过编译器编译生成常规方法,将可以很大程度上提升效率。
!!!注意:在常用场景下你可以认为是深拷贝的,但是你仔细去看,其实并不是完全深拷贝的,它的拷贝和Spring BeanUtils是一个样的,那就是第一层的普通对象确实是深拷贝,如果你属性里是引用对象的(比如List),那这种字段的拷贝其实是浅拷贝的,只拷贝了引用。但是它这种拷贝方式,在平时业务都是够用的,且性能高,如果要完全深拷贝请使用序列化反序列化的方式来做。例如FastJSON

  • JavaBean 问题引入
    在开发的时候经常会有业务代码之间有很多的 JavaBean 之间的相互转化,比如PO/DTO/VO/QueryParam之间的转换问题。之前我们的做法是:

  • 常见拷贝技术
    org.apache.commons.beanutils.PropertyUtils.copyProperties
    org.apache.commons.beanutils.BeanUtils.copyProperties
    org.springframework.beans.BeanUtils.copyProperties
    net.sf.cglib.beans.BeanCopier

  • 纯get/set
    辅助IDE插件拷贝对象时可以自动set所有方法字段 (这种方式可能有些开发人员不清楚)
    不仅看上去冗余添加新的字段时依然需要手动
    开发效率比较低

  • MapStruct 带来的改变
    MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
    工具可以帮我们实现 JavaBean 之间的转换, 通过注解的方式。

同时, 作为一个工具类,相比于手写, 其应该具有便捷, 不容易出错的特点。

  • MapStrcut实现的原理?
    MapStruct 来生成的代码, 其类似于人手写。 速度上可以得到保证。

前面例子中生成的代码可以在编译后看到, 在 target/generated-sources/annotations 里可以看到; 同时真正在代码包执行的可以在target/classes包中看到。
在这里插入图片描述


import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-01-23T16:05:23+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
public class ABMapperImpl implements ABMapper {

    @Override
    public BObject mapAtoB(AObject aObject) {
        if ( aObject == null ) {
            return null;
        }

        BObject bObject = new BObject();

        bObject.setA( aObject.getA() );
        bObject.setB( aObject.getB() );
        bObject.setC( aObject.getC() );
        bObject.setD( aObject.getD() );
        bObject.setE( aObject.getE() );
        bObject.setF( aObject.getF() );
        bObject.setG( aObject.getG() );
        bObject.setH( aObject.getH() );
        bObject.setI( aObject.getI() );
        List<ItemVo> list = aObject.getList();
        if ( list != null ) {
            bObject.setList( new ArrayList<ItemVo>( list ) );
        }

        return bObject;
    }
}

测试环境

项目:jdk1.8、maven3.6、idea编辑器、springboot2.2.x
测试对象

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class AObject {
    private String a;
    private Integer b;
    private Long c;
    private Double d;
    private Float e;
    private Boolean f;
    private Character g;
    private Byte h;
    private Short i;
    private List<ItemVo> list;
}

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class BObject {
    private String a;
    private Integer b;
    private Long c;
    private Double d;
    private Float e;
    private Boolean f;
    private Character g;
    private Byte h;
    private Short i;
    private List<ItemVo> list;
}

/**
 * @author: humorchen
 * date: 2024/1/22
 * description:
 **/
@Data
public class ItemVo {
    private String a;
    private Integer b;
}

测试样例对象

    /**
     * 获取AObject
     *
     * @return
     */
    public static AObject getA() {
        AObject aObject = new AObject();
        aObject.setA("a");
        aObject.setB(1);
        aObject.setC(1L);
        aObject.setD(1d);
        aObject.setE(1f);
        aObject.setF(true);
        aObject.setG('g');
        aObject.setH((byte) 1);
        aObject.setI((short) 1);
        List<ItemVo> objects = new ArrayList<>();
        ItemVo itemVo = new ItemVo();
        itemVo.setA("1");
        itemVo.setB(1);
        objects.add(itemVo);
        aObject.setList(objects);
        return aObject;
    }

测试次数:执行100万次复制

运行设备CPU:Intel® Core™ i7-9700 CPU @ 3.00GHz 3.00 GHz 8核心8线程,内存16GB

MapStruct

相对麻烦点,要写mapper接口,然后会在编译的时候生成接口实现类的字节码文件,相当于你定义接口,它帮你实现手写set方法代码去复制值,执行时没有用反射了,效率更高

maven依赖

mapstruct 1.4.2.Final版本

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>

额外需要配置maven的build,不配置这个执行的时候会报错找不到实现类,因为编译构建期间没有给你生成实现类的字节码,我这里还使用了lombok(1.18.6版本),不懂 lombok的可以百度下,很简单。

<build>
        <finalName>recovery-operation</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

            <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>
                    <!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html -->
                    <!-- Classpath elements to supply as annotation processor path. If specified, the compiler   -->
                    <!-- will detect annotation processors only in those classpath elements. If omitted, the     -->
                    <!-- default classpath is used to detect annotation processors. The detection itself depends -->
                    <!-- on the configuration of annotationProcessors.                                           -->
                    <!--                                                                                         -->
                    <!-- According to this documentation, the provided dependency processor is not considered!   -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.4.2.Final</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.6</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>

    </build>

百万次拷贝耗时998ms

测试代码如下

/**
 * testMapperCopy
 * MapperCopy:998 ms
 */
@Test
public void testMapperCopy() {
    ArrayList<AObject> aObjects = new ArrayList<>();
    for (int i = 0; i < max; i++) {
        aObjects.add(getA());
    }
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    List<BObject> bObjects = aObjects.stream().map(ABMapper.INSTANCE::mapAtoB).collect(Collectors.toList());
    stopWatch.stop();
    System.out.println("MapperCopy:" + stopWatch.getTotalTimeMillis() + " ms");
}

Github地址

MapStruct https://github.com/mapstruct/mapstruct

深度了解推荐阅读

常用开发库 - MapStruct工具库详解 https://zhuanlan.zhihu.com/p/357402881?utm_id=0
MapStruct官网

posted @ 2024-01-23 16:28  HumorChen99  阅读(257)  评论(0编辑  收藏  举报  来源