告别BeanUtils,MapStruct为你敞开大门

95 阅读5分钟

如果你现在还在使用BeanUtils,看了本文,也会像我一样,从此改用Mapstruct。

先上结论,Mapstruct的性能远远高于BeanUtils,这应该是大佬使用Mapstruct的主要原因,随着属性个数的增加,BeanUtils的耗时也在增加,并且BeanUtils的耗时跟属性个数成正比,而Mapstruct的耗时却一直是1秒,所以从对比数据可以看出Mapstruct是非常优秀的,其性能远远超过BeanUtils

image.png

Mapstruct依赖

使用Mapstruct需要依赖的包如下,mapstruct、mapstruct-processor、lombok

<!--lombok要放到mapstruct上面  后者需要前者提供资源-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.2.Final</version>
</dependency>

简单的属性拷贝

当两个对象的属性类型和名称完全相同时,Mapstruct会自动拷贝;假设我们现在需要把UserPo的属性值拷贝到UserEntity中,我们需要做下面几件事情:

  1. 定义UserPo和UserEntity
  2. 定义转换接口
  3. 编写测试main方法

首先定义UserPo和UserEntity

UserPo和UserEntity的属性类型和名称完全相同。

package mapstruct;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserPo {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick;
    private String userVerified;
}
package mapstruct;

import lombok.Data;

import java.util.Date;

@Data
public class UserEntity {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick;
    private String userVerified;
}

定义转换接口

定义mapstruct接口,在接口上打上@Mapper注解。

接口中有一个常量和一个方法,常量的值是接口的实现类,这个实现类是Mapstruct默认帮我们实现的,下文会讲到。定义了一个po2entity的转换方法,表示把入参UserPo对象,转换成UserEntity。

注意@Mapper是Mapstruct的注解,不要引错了。

package mapstruct;

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface IPersonMapper {
    IPersonMapper INSTANCT = Mappers.getMapper(IPersonMapper.class);
    UserEntity po2entity(UserPo userPo);
}

测试类

创建一个UserPo对象,并使用Mapstruct做转化

package mapstruct;


import org.springframework.beans.BeanUtils;

import java.util.Date;
public class MapStructTest {
    public static void main(String[] args) {
        testNormal();
    }

    public static void testNormal() {
        System.out.println("-----------testNormal-----start------");
        UserPo userPo = UserPo.builder()
                .id(1L)
                .gmtCreate(new Date())
                .buyerId(666L)
                .userNick("测试mapstruct")
                .userVerified("ok")
                .age(18L) 
                .build();
        System.out.println("1234" + userPo);
        UserEntity userEntity = IPersonMapper.INSTANCT.po2entity(userPo);
        System.out.println(userEntity);
        System.out.println("-----------testNormal-----ent------");
    }
}

测试结果

可以看到,所有赋值的属性都做了处理,且两边的值都一样,结果符合预期。

b1232586d23040d69f6f938774018f8d~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

Mapstruct性能优于BeanUtils的原因

Java程序执行的过程,是由编译器先把java文件编译成class字节码文件,然后由JVM去解释执行class文件。Mapstruct正是在java文件到class这一步帮我们实现了转换方法,即做了预处理,提前编译好文件,如果用过lombok的同学一定能理解其好处,通过查看class文件,可以看出IPersonMapper被打上org.mapstruct.Mapper注解后,编译器自动会帮我们生成一个实现类IPersonMapperImpl,并实现了po2entity这个方法,看下面的截图。

IpersonMapperImpl代码

从生成的代码可以看出,转化过程非常简单,只使用了UserPo的get方法和UserEntity的set方法,没有复杂的逻辑处理,清晰明了,所以性能很高。

下面再去看BeanUtils的默认实现。

5d6b4ad0073349048f0165ec9100e09f~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

属性类型相同名称不同

对于属性名称不同的属性进行处理时,需要使用@Mapping,比如修改UserEntity中的userNick为userNick1,然后进行转换。

修改UserEntity属性userNick1

package mapstruct;

import lombok.Data;

import java.util.Date;

@Data
public class UserEntity {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick1;
    private String userVerified;
}

@Mapping注解指定source和target字段名称对应关系

@Mapping(target = "userNick1", source = "userNick"),此处的意思就是在转化的过程中,将UserPo的userNick属性值赋值给UserEntity的userNick1属性。

package mapstruct;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface IPersonMapper {
    IPersonMapper INSTANCT = Mappers.getMapper(IPersonMapper.class);
    @Mapping(target = "userNick1", source = "userNick")
    UserEntity po2entity(UserPo userPo);}

执行结果

可以看到,正常映射,符合预期。

图片

String转日期&String转数字&忽略某个字端&给默认值等

@Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd")
@Mapping(target = "age", source = "age", numberFormat = "#0.00")
@Mapping(target = "id", ignore = true)
@Mapping(target = "userVerified", defaultValue = "defaultValue-2")

查看class实现类

  1. createTime:可以看到对日期使用了SimpleDateFormat进行转换,这里建议不要使用这个,因为每次都创建了一个SimpleDateFormat,可以参考《阿里巴巴Java开发手册》关于日期转换的建议。
  2. age:字符串转数字,也是帮忙做了处理
  3. id:字段赋值没有了
  4. userVerified:如果为null赋值默认值

自定义转换

如果现有的能力都不能满足需要,可以自定义一个转换器,比如我们需要把一个字符串使用JSON工具转换成对象。

添加属性

我们在po中加入一个字符串的attributes属性,在entity中加入Attributes类型的属性。

package mapstruct;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Attributes {
    private Long id;
    private String name;
}
package mapstruct;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructorpublic class UserPo {
    private Long id;
    private Date gmtCreate;
    private String createTime;
    private Long buyerId;
    private String age;
    private String userNick;
    private String userVerified;
    private String attributes;
}
package mapstruct;

import lombok.Data;

import java.util.Date;

@Data
public class UserEntity {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick1;
    private String userVerified;
    private Attributes attributes;
}

编写自定义转换处理类

转换器很简单,就是一个普通的Java类,只要在方法上打上Mapstruct的注解@Named。

package mapstruct;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Named;

public class AttributeConvertUtil {
    /**
     * json字符串转对象
     *
     * @param jsonStr
     * @return
     */
    @Named("jsonToObject")
    public Attributes jsonToObject(String jsonStr) {
        if (StringUtils.isEmpty(jsonStr)) {
            return null;
        }
        return JSONObject.parseObject(jsonStr, Attributes.class);
    }
}

修改转换接口

  1. 在@Mapper上引用我们的自定义转换代码类AttributeConvertUtil
  2. 使用qualifiedByName指定我们使用的自定义转换方法
package mapstruct;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(uses = AttributeConvertUtil.class)
public interface IPersonMapper {
    IPersonMapper INSTANCT = Mappers.getMapper(IPersonMapper.class);
    @Mapping(target = "attributes", source = "attributes", qualifiedByName = "jsonToObject")
    @Mapping(target = "userNick1", source = "userNick")
    @Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd")
    @Mapping(target = "age", source = "age", numberFormat = "#0.00")
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "userVerified", defaultValue = "defaultValue-2")
    UserEntity po2entity(UserPo userPo);
}

测试类及结果

可以看出我们将把String转成了JSON对象

public class MapStructTest {
    public static void main(String[] args) {
        testNormal();
    }
    
    public static void testNormal() {
        System.out.println("-----------testNormal-----start------");
        String attributes = "{"id":2,"name":"测试123"}";
        UserPo userPo = UserPo.builder()
                .id(1L)
                .gmtCreate(new Date()) 
                .buyerId(666L)
                .userNick("测试mapstruct") 
                .userVerified("ok") 
                .age("18") 
                .attributes(attributes) 
                .build();
        System.out.println("1234" + userPo);
        UserEntity userEntity = IPersonMapper.INSTANCT.po2entity(userPo);        
        System.out.println(userEntity);
        System.out.println("-----------testNormal-----ent------");
    }
 }

图片

查看实现类

可以看到,在实现类中Mapstruct帮我们new了一个AttributeConvertUtil的对象,并调用了该对象的jsonToObject方法,将字符串转成JSON,最终赋值给了UserEntity的attributes属性,实现很简单,也是我们可以猜到的。

b8806b22eb104f5e92512102a295d93b~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp