[Java] Mapstruct-Plus:基于MapStruct的、增强版的Java数据对象映射框架

概述: Mapstruct-Plus

简介

  • MapStructPlus 可能是最简单最强大的Java Bean转换工具
  • Mapstruct PlusMapstruct 的增强工具,在 Mapstruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更加便捷、优雅。
  • Mapstruct Plus 内嵌 Mapstruct,和 Mapstruct 完全兼容,如果之前已使用 Mapstruct,可以无缝替换依赖
  • Author : 代码笔耕
  • URL

Java数据对象转换工具的发展背景与发展过程

  • 现代企业级软件开发中,随着应用架构的复杂化业务逻辑数据模型的层次也日益增多。

VO(View Object)、DTO(Data Transfer Object)、PO(Persistent Object)、BO(Business Object)等对象层次的转换需求变得极为普遍。
传统方法如sh调用手动 getter/setter 或使用 Apache Commons BeanUtils 进行对象复制,虽然实现简单,但维护成本高且容易引入错误性能也不尽如人意。

  • MapStruct 的出现,通过注解处理器编译期生成高效的映射代码,极大地简化了对象之间的转换

  • MapStruct Plus 则进一步增强了这一功能,特别是通过 @AutoMapper 注解,使得双向对象转换变得更加轻松和直观。

深入解析 MapStruct Plus 的 @AutoMapper 注解及其对象映射机制

@AutoMapper 注解的核心概念

  • @AutoMapperMapStruct Plus 提供的一个关键注解,用于简化 Java Bean对象之间的转换任务。

通过在一个类上添加该注解,开发者无需手动编写复杂的映射逻辑,就能自动生成两个类之间的转换接口。
该注解的出现极大地减少了代码冗余,提升了代码的可维护性和可读性。

工作原理

  • @AutoMapper 注解的核心工作机制基于注解处理器Annotation Processor)。
  • 在编译期,MapStruct Plus 的注解处理器会扫描带有 @AutoMapper 注解的类,自动生成两个类之间的映射代码。
  • 注解处理器的这种编译期处理方式保证了转换代码的高效性,避免了运行时的反射调用,从而提升了性能

MapStruct Plus 的特性

  • 与 MapStruct 完全兼容:MapStruct Plus 是对 MapStruct 的增强扩展。如果你在项目中已经使用了 MapStruct,可以直接替换相关依赖以引入 MapStruct Plus,无需对现有代码进行大幅度修改。

  • 双向转换的单注解实现:通过一个 @AutoMapper 注解,就可以同时生成两个类之间的双向转换代码,减少了注解的使用频率和复杂度。

  • 高效的编译期生成:所有的映射逻辑都在编译期生成,完全避免了运行时的性能损耗,相比于反射等动态调用方式,性能更高。

Maven 依赖

引入方式1 清爽版

<!-- data object convert | start -->
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct</artifactId>
	<version>${mapstruct.version}</version>
</dependency>
<!-- mapstruct-processor | 避免报错: Caused by: java.lang.ClassNotFoundException: Cannot find implementation for com.daq.sdk.pojo.mapstruct.convert.CustomerBeanMapper -->
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct-processor</artifactId>
	<version>${mapstruct.version}</version>
</dependency>

<dependency>
  <groupId>io.github.linpeilie</groupId>
  <artifactId>mapstruct-plus</artifactId>
  <version>${mapstruct-plus.version}</version>
</dependency>
<dependency>
	<groupId>io.github.linpeilie</groupId>
	<artifactId>mapstruct-plus-object-convert</artifactId>
	<version>${mapstruct-plus.version}</version>
</dependency>
<!-- data object convert | end -->
  • mapstruct.version : 1.5.5.Final
  • mapstruct-plus.version : 1.4.6
  • spring-boot : 3.3.5 (经项目验证过的配套版本,了解即可)
  • spring : 6.1.14 (经项目验证过的配套版本,了解即可)

引入方式2 SpringBoot适配版

<properties>
    <java.version>19</java.version>
    <mapstruct-plus.version>1.3.5</mapstruct-plus.version>
    <lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
    <!-- knife4j 版本4.0.0——SpringBoot版本兼容性 -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
        <version>4.0.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <!-- maven-compiler-plugin作用:指定 Java 版本、设置编码方式 -->
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>io.github.linpeilie</groupId>
                        <artifactId>mapstruct-plus-processor</artifactId>
                        <version>${mapstruct-plus.version}</version>
                    </path>
                    <!-- 将 Lombok 注解和 MapStruct 的映射注解进行绑定,使它们能够协同工作 -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

案例实践

案例:单个类与单个类之间的映射(User => UserDto)

依赖引入

假定引入本文所指的【SpringBoot版】的依赖

User / UserDto : 实体类

package com.example.mapstructplusdemo.model.entity;

import io.github.linpeilie.annotations.AutoMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * @Description : 实体类 User
 */
@ToString //lombok 注解
@Data //lombok 注解
@Accessors(chain = true) //lombok 注解
@AllArgsConstructor //lombok 注解
@NoArgsConstructor //lombok 注解
@AutoMapper(target = UserDto.class) // 重点 (MapStruct Plus 新增的注解)
public class User {
    private String username;
    private int age;
    private boolean young;
}


@ToString
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Data
public class UserDto {
    private String username;
    // 属性名称不同时.
    @AutoMapping(target = "age")  
    private int ageB;
    private boolean young;
}

测试类

package com.example.mapstructplusdemo;

import com.example.mapstructplusdemo.model.entity.User;
import com.example.mapstructplusdemo.model.entity.UserDto;
import io.github.linpeilie.Converter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MapstructPlusDemoApplicationTests {

    @Autowired
    private Converter converter;

    @Test
    void contextLoads() {
        User user = new User()
			.setUsername("jack")
			.setAge(23)
			.setYoung(false);

        UserDto userDto = converter.convert(user, UserDto.class);
        System.out.println("userDto: "+ userDto);

        /**
         * 断言语句作用:验证代码的正确性。当断言失败时,会抛出AssertionError异常,
         *          提醒我们假设不成立,需修正。
         * 上线时,需删除,以提高代码的执行效率。
         */
        assert user.getUsername().equals(userDto.getUsername());
        assert user.getAge() == userDto.getAge();
        assert user.isYoung() == userDto.isYoung();

        User newUser = converter.convert(userDto, User.class);

        System.out.println(newUser);

        assert user.getUsername().equals(newUser.getUsername());
        assert user.getAge() == newUser.getAge();
        assert user.isYoung() == newUser.isYoung();
    }
}

MapStruct 的易用性高很多(需要自己编写的代码量更少了————弊端:但也增加了一重黑盒)

案例:单个类与单个类之间的映射(Car => CarDto)【必读】

@AutoMapper(target = CarDto.class)
public class Car {
    // ...
}
  • 该例子表示,会生成 Car 转换为 CarDto 的接口 CarToCarDtoMapper 及实现类 CarToCarDtoMapperImpl

自动生成的转换代码中,源类型Car)的所有可读属性(即 不含private属性)将被复制到目标属性类型(CarDto)的相应属性中。

  • 当一个属性与它的目标实体对应物具有相同的名称时,将会被隐式映射

  • 除此之外,MapStructPlus 会根据当前的默认规则,生成 CarDto 转换为 Car 的接口 CarDtoToCarMapper 及实现类 CarDtoToCarMapperImpl

如果不想生成该转换逻辑的话,可以通过注解的 reverseConvertGenerate 属性来配置。

案例:单个类与多个类之间的映射(UserDto、UserVo => User)

@Data
@AutoMappers({
    @AutoMapper(target = UserDto.class),
    @AutoMapper(target = UserVO.class)
})
public class User {
    // fields
}
  • 当想要配置一个类多个类进行转换时,可以通过 @AutoMappers 来配置,该注解支持配置多个 @AutoMapper

更多场景与案例

参见官方文档

K FAQ for MapStruct-Plus

Q: 解决io.github.linpeilie.ConvertException: cannot find converter from Car to CarDto?

  • 问题描述
  • API 请求地址'/demo/car/list',发生未知异常.
io.github.linpeilie.ConvertException: cannot find converter from Car to CarDto
	at io.github.linpeilie.Converter.convert(Converter.java:31)
	at io.github.linpeilie.Converter.lambda$convert$0(Converter.java:54)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
	at io.github.linpeilie.Converter.convert(Converter.java:54)
	at org.x.common.core.utils.MapstructUtils.convert(MapstructUtils.java:73)
	at org.x.common.mybatis.core.mapper.BaseMapperPlus.selectVoList(BaseMapperPlus.java:174)
	at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$DefaultMethodInvoker.invoke(MybatisMapperProxy.java:166)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
	at jdk.proxy2/jdk.proxy2.$Proxy182.selectVoList(Unknown Source)
  • 解决方法及效果

配置 MapStructPlus 的 @AutoMapper注解

Y 推荐文献

  • MapStruct-Plus

X 参考文献

posted @ 2025-04-12 11:27  千千寰宇  阅读(483)  评论(0)    收藏  举报