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、大批量数据处理)

相关推荐
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒2 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境2 小时前
提升 Python 代码健壮性的方法大盘点
后端·python