还在写Set赋值对象直接的属性值?试试MapStruct吧!

本文介绍了MapStruct在Java开发中的高效对象映射,如何通过编译时生成代码避免繁琐set赋值,提升开发效率并保持性能。特别关注了与Spring BeanUtils和Lombok的整合应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相信很多小伙伴有这样的困扰!!!在开发中,很多时候需要VO和BO或者BO和DO等实体对象之前的赋值转换,以便于后续service层或者dao层实体对象的使用。

正如下图所示,不知道各位看到如何繁多的set赋值第一反应是啥,反正我是挺头疼的。而且这还是只是对象属性赋值的一半不到😂

从理论上讲直接set赋值应该是性能最快的,但是难道就没有什么办法能帮助我们,既能减少如此繁琐且容易出错,甚至遗漏的赋值方式,还能保证性能的方法么?答案当然是肯定有。

可能大家最直观的想法就是使用spring为我们提供的BeanUtils工具类的copyProperties()方法。通过读源码我们知道,该方法包装的Java反射,而且只支持同属性类型和同属性名的对象转换,一旦遇到不同属性类型的对象将自动忽略,导致无法自动赋值。而且如果一个对象的属性字段太多,反射带来的消耗势必将影响性能。

如果之前有频繁使用该工具类的小伙伴,估计遇到过类似如下的情况:

  1. source对象中的类型为包装类型(ex:Integer)
  2. target对象中的类型为基本类型 (ex:int)

此时如果还是直接使用BeanUtils的copyProperties方法,将会报异常java.lang.IllegalArgumentException。

当然这可能是最常见的一个例子之一。再比如空格问题,如果在source对象中,属性值中包含空格,在copyProperties后空格会保留,如果最后持久化到数据库中,极大可能会对后续操作造成影响。

另外一个类似的工具类的那就是Apache的commons.beanUtils,这个工具类功能和spring的BeanUtils类似。据说是有不少问题,而且性能也拉胯,何况阿里巴巴开发规范明确规定不允许使用Apache的该工具类进行属性的copy。有兴趣的小伙伴可以研究一下。

当然还有诸如Cglib BeanCpoier以及Apache的PropertyUtils,均不是本文的重点,故不做具体介绍。有兴趣的可以自行研究。

有了上面的问题,那么就要引出我们今天的主角:MapStruct

我们先看一下官网是如何介绍MapStruct的

乍一看认为无非就是和那几个工具类一样么,省去写大量的set方法,简化代码等。但是,朋友们注意这一句:与其他映射框架相比,MapStrut在编译时生成bean映射,以确保高性能,允许快速的开发人员反馈和彻底的错误检查

这是什么意思呢?话不多说我们直接 show code。

  • 第一步:创建Maven项目后,我们在pom中引入mapstruct的依赖
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
        </dependency>

 

  • 第二步:在pom的build标签下添加mapstruct的processor,用于在编译时由mapstruct帮助我们生成对象映射
    <build>
        <pluginManagement>
            <plugins>
                <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>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.mapstruct</groupId>
                                <artifactId>mapstruct-processor</artifactId>
                                <version>1.4.2.Final</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
  • 第三步:添加好依赖后,我们开始创建对象。搞一个BO和DO,然后将BO的属性值复制给DO。
public class UserBO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    //省略get/set方法
}
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
    //省略get/set方法
}
  • 第四步:关键步骤,创建一个接口,用于让MapStruct将BO对象转换为DO对象,并正确赋值。其中@Mapper注解标识这是一个MapStruct对象属性转换接口,在编译时MapStruct会自动生成该接口的实现类。Mappers.getMapper()方法用于自动生成实现类的实例。
@Mapper
public interface MapStructConvertUtil {
    MapStructConvertUtil INSTANCE = Mappers.getMapper(MapStructConvertUtil.class);
    UserDO convert(UserBO userBO);
}
  • 第五步:测试看效果:  

Ohhhhhhh!!!竟然真的做到了。

什么原因呢?看一下build后的文件,惊奇的发现居然自动生成了一个接口的实现类。

打开后,不觉明历,原来如此。MapStruct不仅自动的生成了接口的实现类,还将我们convert方法的target对象和source对象做了属性匹配。这不恰恰就是我们自己不想写的繁琐的set赋值么。

以此我们也可以推断,相较于直接set方法赋值,使用MapStruct对性能会稍稍有一点影响,就在于生成接口的实现类上,但是理论上该影响会很小,几乎可以保持和直接set赋值一样的性能。后续有时间可以测试一下性能,看看实际差距有多大。

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-06-15T23:10:21+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
)
public class MapStructConvertUtilImpl implements MapStructConvertUtil {

    @Override
    public UserDO convert(UserBO userBO) {
        if ( userBO == null ) {
            return null;
        }

        UserDO userDO = new UserDO();

        userDO.setName( userBO.getName() );
        userDO.setId( userBO.getId() );
        userDO.setAddress( userBO.getAddress() );
        userDO.setEmail( userBO.getEmail() );
        userDO.setAge( userBO.getAge() );
        userDO.setGender( userBO.getGender() );

        return userDO;
    }
}

下面我们再做进一步的扩展研究。实际项目中有部分小伙伴项目中可能会用到Lombok,更进一步的简化代码。那么Lombok和MapStruct怎么一起使用呢,仅仅是引入依赖,实体加上@Data注解就OK了么?答案是:NO!!!

我们直接看官网,官网的FAQ下有对应的问题解决方案,并且给出了示例

打开示例的pom文件,可以看到不仅需要引入Lombok依赖,还需要在build中添加Lombok,以及lombok-mapstruct-binding。

<?xml version="1.0" encoding="UTF-8"?>
<!--
 Copyright MapStruct Authors.
 Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.mapstruct.examples.lombok</groupId>
    <artifactId>mapstruct-examples-lombok</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.20</org.projectlombok.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <!-- lombok dependencies should not end up on classpath -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
<!--        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <scope>provided</scope>
        </dependency>-->

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.13.1</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <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>${org.mapstruct.version}</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                                <version>${org.projectlombok.version}</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok-mapstruct-binding</artifactId>
                                <version>${lombok-mapstruct-binding.version}</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

使用官网的demo,我们测试一下整合了lombok的mapstruct的效果。依旧使用UserBO和UserDO这两个实体,稍稍做一些改动。

@Data
public class UserBO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
}
@Data
public class UserDO {
    private String name;
    private Integer id;
    private String address;
    private String email;
    private Integer age;
    private String gender;
}

转换接口和测试方法都不变,跑一遍code,看效果。依旧可以实现对象属性的复制。

简单总结一下:

  1. 使用高版本的idea集成mapstruct,只需要引入mapstruct的依赖,增加mapstruct的转换接口即可。低版本的idea(2018.1.1)则需要将依赖mapstruct-processor也引入
  2. 集成lombok和mapStruct,不仅需要引入lombok依赖,也需要在build中加入lombok和lombok-mapstruct-binding,用于在builder时自动处理get/set,以及处理lombok和mapstruct的绑定顺序。有兴趣的小伙伴可以试试build中不加ombok-mapstruct-binding依赖,在自动生成的接口实现类型中,就不会有2个对象的属性赋值,只会new一个空的target对象,直接返回。原因就是没有做绑定关系,先生成了接口实现类,而后lombok才处理了get/set。所以测试对象的值都没有赋值成功。
    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2021-06-16T09:26:01+0800",
        comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_292 (Amazon.com Inc.)"
    )
    public class MapStructConvertUtilImpl implements MapStructConvertUtil {
    
        @Override
        public UserDO convert(UserBO userBO) {
            if ( userBO == null ) {
                return null;
            }
    
            UserDO userDO = new UserDO();
            return userDO;
        }
    }
  3. 本文只做了入门的基本使用,对于复杂的对象属性,如何使用mapstruct处理,将在《MapStruct进阶使用(二)》中介绍,欢迎围观。 

最后,如果觉得本文对你有用,请不要吝啬你的素质三连,你的鼓励就是我创作的最大动力,感谢阅读。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值