BeanUtils 你走好!MapStruct 才是对象转换的真香神器!

前言

在日常开发中,对象之间的属性拷贝是一个非常常见的需求。我们经常需要将 DTO 转换为 Entity,或者把 VO 映射到 DTO,这种场景下很多人还在使用 BeanUtils.copyProperties 或者 Apache Commons 提供的工具类

但今天我想告诉你:是时候告别这些老古董了!MapStruct 才是你应该选择的对象映射神器!

为什么我不再推荐使用 BeanUtils?

性能堪忧

BeanUtils.copyProperties 是通过 反射机制 实现的,虽然写起来简单,但性能很差。尤其是在循环或处理大批量数据时,反射调用会导致严重的性能瓶颈。

类型不安全

BeanUtils 不做编译期检查,如果字段名拼错了,或者类型不匹配,只有在运行的时候才会抛出异常。这在大型项目中非常危险。

功能单一,难以扩展

当遇到字段名不同、嵌套对象、枚举转换等复杂情况时,BeanUtils 几乎无能为力,你需要手动编写大量代码来处理这些问题。

MapStruct 是什么?它为什么这么强?

MapStruct 是一个代码生成器,它基于注解处理器,在编译阶段自动生成 Java Bean 的映射实现类,完全避免了反射带来的性能损耗。

🎯 一句话总结:MapStruct = 编译期安全 + 零反射 + 极致性能 + 可扩展性强。

快速上手

添加依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.9.4</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

定义DO 和 DTO

less 复制代码
@Setter
@Getter
public class User {
    private long id;
    private String name;
    private int age;
}
less 复制代码
@Setter
@Getter
@ToString
public class UserDTO {
    private String name;
    private int age;
}

定义 Mapper 接口

java 复制代码
@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserDTO toDTO(User user);
}

编译后,MapStruct 会自动生成一个 UserMapperImpl 类,里面就是直接赋值的代码,没有反射!

使用示例

scss 复制代码
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
    User user = new User();
    user.setId(1L);
    user.setName("Jerry");
    user.setAge(30);
    // mapstruct方案
    UserDTO dto = UserMapper.INSTANCE.toDTO(user);
    System.out.println(dto); // UserDTO(name=Jerry, age=30)
    // BeanUtils方案
    dto = new UserDTO();
    BeanUtils.copyProperties(dto, user);
    System.out.println(dto); // UserDTO(name=Jerry, age=30)
}

BeanUtils方案看似简单,不用定义Mapper,但是背后包含了方法调用,类型检查,效率其实是比较低下的

简单对比

特性 BeanUtils MapStruct
是否反射 ✅ 是 ❌ 否
编译期报错 ❌ 否 ✅ 是
易用性 ✅ 简单 ✅ 简单 + 强大
映射灵活性 ❌ 差 ✅ 支持字段重命名、嵌套等
性能 ⚠️ 慢(尤其大数据) ✅ 极快(接近原生赋值)

MapStruct高级玩法

自定义字段映射

修改Mapper类和User类

less 复制代码
@Setter
@Getter
public class User2 {
    private long id;
    private String userName;
    private int age;
}

自定义字段映射:

kotlin 复制代码
@Mapper
public interface UserMapper2 {

    UserMapper2 INSTANCE = Mappers.getMapper(UserMapper2.class);

    @Mapping(source = "userName",target = "name")
    UserDTO toDTO(User2 user);
}

测试代码:

ini 复制代码
@Test
public void test2(){
    User2 user = new User2();
    user.setId(1L);
    user.setUserName("Jack");
    user.setAge(20);
    UserDTO dto = UserMapper2.INSTANCE.toDTO(user);
    System.out.println(dto);
}

需要User类和DTO类和Mapper类

less 复制代码
@Setter
@Getter
@ToString
public class UserDTO3 {
    private String name;
    private int age;
    private String city;
}
less 复制代码
@Setter
@Getter
public class User3 {
    private long id;
    private String userName;
    private int age;
    private Address address;
}

多合一映射:

less 复制代码
@Mapper
public interface UserMapper3 {
    UserMapper3 INSTANCE = Mappers.getMapper(UserMapper3.class);

    @Mappings({
            @Mapping(source = "userName", target = "name"),
            @Mapping(source = "address.city", target = "city")
    })
    UserDTO3 toDTO(User3 user);
}

测试代码:

ini 复制代码
@Test
public void test3(){
    User3 user = new User3();
    Address address = new Address();
    address.setId("1");
    address.setCity("北京");
    user.setId(1L);
    user.setUserName("Jerry");
    user.setAge(30);
    user.setAddress(address);
    UserDTO3 dto = UserMapper3.INSTANCE.toDTO(user);
    System.out.println(dto);
}

Spring 整合: 只需加一个 componentModel = "spring" 参数即可注入:

kotlin 复制代码
@Mapper(componentModel = "spring") public interface UserMapper { ... }

性能测试

网上都在说MapStruct的性能比BeanUtils好很多,但是很多都只是在说这个结论 我们直接开测,做Java的,一提到压力测试就会想到Jmeter,但是其实还有一个工具:JMH

JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级

上代码:

less 复制代码
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 2)
public class JMHTest {

    private User user;

    @Setup
    public void setup() {
        User user = new User();
        user.setId(1L);
        user.setName("Jerry");
        user.setAge(30);
        this.user = user;
    }

    @Benchmark
    public void testBeanUtilsCopy(Blackhole blackhole) throws InvocationTargetException, IllegalAccessException {
        UserDTO target = new UserDTO();
        BeanUtils.copyProperties(target, user);
        blackhole.consume(target);
    }

    @Benchmark
    public void testMapStructCopy(Blackhole blackhole) {
        UserDTO target = UserMapper.INSTANCE.toDTO(user);
        blackhole.consume(target);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHTest.class.getSimpleName())
                .resultFormat(ResultFormatType.JSON)
                .build();

        new Runner(opt).run();
    }
}

输出结果如下:

Benchmark Mode Cnt Score (ns/op) Error (ns/op) Units
JMHTest.testBeanUtilsCopy avgt 5 10564.333 ± 490.630 ns/op
JMHTest.testMapStructCopy avgt 5 4.784 ± 0.229 ns/op

性能对比:

方法 平均耗时 性能倍数(相对 MapStruct)
BeanUtils.copyProperties ~10.7 μs 慢 2168 倍
MapStruct ~4.9 ns 1 倍(基准)

为什么差距这么大?

特性 BeanUtils MapStruct
实现方式 反射(Reflection) 编译期生成字节码
调用开销 高(每次都要查找 setter/getter) 极低(等同手写 setter)
JIT 优化 难以优化 容易被 JIT 优化
类型安全 否 是
编译检查 否 是

💡 结论 :
BeanUtils.copyProperties 适合低频、简单的 DTO 转换 ;
MapStruct 适合高频、性能敏感的场景(如高并发 API、大批量数据处理)

相关推荐
学长毕设8 小时前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端
Best_Liu~8 小时前
SpringBoot全局日期格式化
java·spring boot·后端
小蒜学长8 小时前
基于Django+Vue喵汪联盟宠物领养系统的设计与实现(代码+数据库+LW)
后端·python·django·宠物领养系统·流程规范化
Dwzun9 小时前
基于SpringBoot3+Vue3的油莎豆种植管理系统
java·前端·vue.js·spring boot·后端·python·计算机网络
小码哥_常15 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
学长毕设17 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
努力的小雨20 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
excel21 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
AI入职记1 天前
AI 代码审查召回率实测:20 个骗过单元测试的变异体,抓到 13 个
后端
浮链序1 天前
别让 AI 客服把用户当猴耍:百来行 Python 把「转人工」做成一条高可用链路
后端