MapStruct Plus,谁用谁喜欢

前言

MapStruct 是一个代码生成工具,它基于 约定优于配置 的原则,极大地简化了 Java Bean 类型之间映射的实现。生成的映射代码使用简单的方法调用,因此具有速度快、类型安全且易于理解的特点。

Mapstruct Plus 是 Mapstruct 的增强工具,在 Mapstruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更加便捷、优雅。

MapStruct Plus 内嵌 MapStruct,和 MapStruct 完全兼容,如果之前已经使用 MapStruct,可以无缝替换依赖。

参考网站:

MapStruct 官网

MapStruct Plus 官网 

一、Why MapStruct

多层架构的应用程序通常需要在不同的对象模型之间进行映射(例如,实体类和DTO)。编写这种映射代码是一项繁琐且容易出错的任务。MapStruct 的目标是通过尽可能地自动化这一过程来简化这项工作。

与其他映射框架不同,MapStruct 在编译时生成 Bean 映射代码,这确保了高性能,同时为开发者提供了快速的反馈和全面的错误检查。

比如一个 User 对象需要转换为 UserVo 对象:

@Data

public class User {

    private String username;

    private int age;

    private boolean young;

}
@Data

public class UserDTO {

    private String username;

    private int age;

    private boolean young;

}

常规的有两种方式:

使用 getter 和 setter 方法进行赋值,但是这个方法有着大量枯燥且重复的工作,一旦出错也不易于发现,可读性差。

使用 spring 提供的 BeanUtils 工具类进行对象之间的转换,如下代码块所示,但是因为内部采用反射实现,性能低下,出现问题时不容易调试。

// 创建一个 User 对象

User user = new User();

user.setUsername("jack");

user.setAge(23);

user.setYoung(false);



// 创建一个 UserDTO 对象

UserDTO userDTO = new UserDTO();

// 一行代码实现 user => userDTO

BeanUtils.copyProperties(user, userDTO);

所以 MapStruct 应运而生,这个框架是基于 Java 注释处理器,定义一个转换接口,在编译的时候会根据接口类和方法相关的注解,自动生成实现类,底层是基于 getter 和 setter 方法的,比 BeanUtils 的性能要高。然而美中不足的是,当需要转换的对象较多或者结构复杂的时候,需要定义较多的转换接口和转换方法。

此时,就可以使用 MapStruct Plus ,一个注解就可以生成两个类之间的转换接口,使 Java 类型转换更加便捷和优雅。

二、MapStruct Plus的快速开始

本文以 Spring Boot 项目为例,版本:

Spring Boot:2.7.6

JDK:8

Lombok:1.18.24

1. 引入依赖

引入 mapstruct-plus-spring-boot-starter 依赖

<mapstruct-plus.version>1.4.3</mapstruct-plus.version>

<dependency>

    <groupId>io.github.linpeilie</groupId>

    <artifactId>mapstruct-plus-spring-boot-starter</artifactId>

    <version>${mapstruct-plus.version}</version>

</dependency>

引入 Maven 插件,配置项目的构建过程

<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>

           <encoding>UTF-8</encoding>



           <!-- MapStruct Plus练习 -->

           <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>

           </annotationProcessorPaths>

       </configuration>

   </plugin>

</plugins>

最新版本依赖可以查看:MapStruct Plus 的 Maven 仓库地址

https://mvnrepository.com/artifact/io.github.linpeilie/mapstruct-plus

2. 指定对象映射关系

在 User 或者 UserDTO 上面增加注解 —— @AutoMapper,并设置 target 为对方类。

以下面代码举例,添加注解:@AutoMapper(target = UserDTO.class)

User 类

@Data

@AutoMapper(target = UserDTO.class)

public class User {

    private String username;

    private int age;

    private boolean young;

}

UserDTO 类

@Data

public class UserDTO{

    private String username;

    private int age;

    private boolean young;

}

3. 编写测试代码

@SpringBootTest

public class QuickStartTest {



    @Autowired

    private Converter converter;



    @Test

    void testMapStructPlus() {



        // 创建一个 User 对象

        User user = new User();

        user.setUsername("jack");

        user.setAge(23);

        user.setYoung(false);



        UserDTO userDTO = converter.convert(user, UserDTO.class);

        System.out.println(userDTO);    // UserDTO{username='jack', age=23, young=false}



        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);    // User{username='jack', age=23, young=false}



        assert user.getUsername().equals(newUser.getUsername());

        assert user.getAge() == newUser.getAge();

        assert user.isYoung() == newUser.isYoung();

    }

}

4. 运行结果

测试通过,输出:

5. 原理解析

通过以上示例可以看出,User 对象转化为 UserDTO 对象主要是UserDTO userDTO = converter.convert(user, UserDTO.class);这行代码,其底层也很简单,原理是通过 getter 和 setter 实现的:

public UserDTO convert(User arg0) {

    if ( arg0 == null ) {

        return null;

    }



    UserDTO userDTO = new UserDTO();



    userDTO.setUsername( arg0.getUsername() );

    userDTO.setAge( arg0.getAge() );

    userDTO.setYoung( arg0.isYoung() );



    return userDTO;

}

该代码被保存在 target 包中,具体路径:target/generated-sources/annotations/实体类存放路径

通过上图,可以看到,除此之外,MapStructPlus 会根据当前的默认规则,生成 UserDTO 转换为 User 的接口 UserDTOToUserMapper 及实现类 UserDTOToUserMapperImpl。如果不想生成该转换逻辑的话,可以通过注解的 reverseConvertGenerate 属性来配置。

三、自定义实体类中的属性转换

在上面的例子中,两个实体类中对应的属性都是同一种类型,那么想要自定义属性比如:后端存储的是字符串 String 类型的属性,想给前端返回一个 List 类型的属性,可以根据规则进行转换。

下面的举例是 String 属性和 List 属性之间的相互转化(String 《===》List)

有两种方式:

自定义一个类型转换器,通过 @AutoMapper 的 uses 属性引入

通过 @AutoMapping 中配置的 expression 表达式配置

1. 自定义一个类型转换器

首先定义两个类型转换器,一个是 String 转为 List,一个是 List 是 String。且两个类型转换器需要定义为 Spring 的 Bean,即使用 @Component 注解。

String 转为 List 的转换器:

@Component

public class StringToListConverter {



    public List<String> stringToList(String str) {

        if (str == null) {

            return Collections.emptyList();

        }

        return Arrays.asList(str.split(","));

    }

}

List 转为 String 的转换器:

@Component

public class ListToStringConverter {



    public String listToString(List<String> list) {

        if (list == null || list.isEmpty()) {

            return null;

        }

        return String.join(",", list);

    }



}

2. 使用类型转换器

第二步,使用该类型转换器,即在 @AutoMapper 注解中使用 uses,且给需要转化的属性加上 @AutoMapping 注解,target 指向另一个需要转化的属性。

User 类:

@Data

@AutoMapper(target = UserDTO.class, uses = StringToListConverter.class)

public class User {

    private String username;

    private int age;

    private boolean young;

    @AutoMapping(target = "tagList")

    private String tags;

}

UserDTO 类:

@Data

@AutoMapper(target = User.class, uses = ListToStringConverter.class)

public class UserDTO {

    private String username;

    private int age;

    private boolean young;

    @AutoMapping(target = "tags")

    private List<String> tagList;

}

3. 进行测试

第三步,进行测试。

@SpringBootTest

public class QuickStartTest {



    @Autowired

private Converter converter;



@Test

void testMapStructPlus() {

    // 创建一个对象

    User user = new User();

    user.setUsername("jack");

    user.setAge(23);

    user.setYoung(false);

    user.setTags("Java,Python,C++");



    // 转换

    UserDTO userDTO = converter.convert(user, UserDTO.class);

    System.out.println(userDTO);

    assert userDTO.getTagList().size() == 3;

}

}

测试结果:

测试用例通过,User 类中的 String 类型的 tags 属性,成功转化为 UserDTO 类中的 List 类型的 tagList 属性。

还有一种方法是直接在注解中写表达式,但是博主觉得这种方式没有自定义转换器好,所以在本文中不列举

如果感兴趣,详情请参考:表达式自定义属性转换

https://www.mapstruct.plus/guide/class-convert.html#%E8%A1%A8%E8%BE%BE%E5%BC%8F

四、Map转为Object

MapStruct Plus 提供了 Map<String, Object> 转化为对象的功能。

转换逻辑:

针对目标类中的一个属性,首先会判断 Map 中是否存在该键,如果存在的话,首先判断类型,

如果类型相同,直接强转

若果类型不同,会使用 Hutool 提供的类型转换工具尝试转换为目标类型

MapStruct Plus 在 1.4.0+ 版本取消了内置 Hutool 框架,如果需要用到 Map 转化为对象的功能时,需要引入 hutool-core 这个依赖

<dependency>

   <groupId>cn.hutool</groupId>

   <artifactId>hutool-core</artifactId>

   <version>5.8.29</version>

</dependency>

1. 使用步骤

引入 hutool-core 依赖

在目标类上添加 @AutoMapMapper 注解

同时支持自定义类作为属性,需要在自定义类上增加 @AutoMapMapper 注解

2. 定义对象

定义两个对象:MapModelA 和 MapModelB

MapModelA 类:

@AutoMapMapper

@Data

public class MapModelA {



    private String str;

    private int i1;

    private Long l2;

    private MapModelB mapModelB;

}

MapModelB类:

@AutoMapMapper

@Data

public class MapModelB {



    private Date date;

}

3. 转换测试

@SpringBootTest

public class MapToObjectTest {



    @Autowired

private Converter converter;



@Test

void testMapStructPlus() {

    Map<String, Object> mapModel1 = new HashMap<>();

    mapModel1.put("str", "1jkf1ijkj3f");

    mapModel1.put("i1", 111);

    mapModel1.put("l2", 11231);



    Map<String, Object> mapModel2 = new HashMap<>();

    mapModel2.put("date", DateUtil.parse("2025-03-12 11:59:59"));



    mapModel1.put("mapModelB", mapModel2);



    final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class);

    System.out.println(mapModelA);  // MapModelA(str=1jkf1ijkj3f, i1=111, l2=11231, mapModelB=MapModelB(date=2023-02-23 01:03:23))

}

}

测试成功,Map 对象成功转化为 MapModelA对象:

五、枚举类型转换

枚举自动转换当前特性从 1.2.2 开始支持

当需要进行枚举转换时(例如枚举转换为编码值,或者由编码转换为枚举),可以在目标枚举添加 @AutoEnumMapper 注解, 增加该注解后,在任意类型中需要转换该枚举时都可以自动转换。

使用该注解需要注意:当前枚举必须有一个可以保证唯一的字段,并在使用当前注解时,将该字段名,添加到注解提供的 value 属性中。

还有就是枚举和使用枚举的类,要在同一个模块中

1. 定义一个枚举类

@Getter

@AllArgsConstructor

@AutoEnumMapper("state")

public enum GoodsStateEnum {



    ENABLED(1, "启用"),

    DISABLED(0, "禁用");



    private final Integer state;

    private final String desc;

}

2. 定义要转换的对象

Goods类:

@Data

@AutoMapper(target = GoodsVo.class)

public class Goods {



    private GoodsStateEnum state;

}

GoodsVO类:

@Data

public class GoodsVO {

    private Integer state;

}

3. 转换测试

@SpringBootTest

public class EnumToValueTest {



    @Autowired

private Converter converter;



@Test

void testMapStructPlus() {



    Goods goods = new Goods();

    goods.setState(GoodsStateEnum.ENABLED);



    final GoodsVO goodsVO = converter.convert(goods, GoodsVO.class);

    System.out.println(goodsVO);

    Assert.equals(goodsVO.getState(), goods.getState().getState());



    final Goods goods2 = converter.convert(goodsVO, Goods.class);

    System.out.println(goods2);

    Assert.equals(goods2.getState(), GoodsStateEnum.ENABLED);

}

}

测试成功,Enum 可以转化为整形,整形也可以转化为 Enum:

4. 跨模块支持

当枚举与要使用的类型,不在同一个模块(module)中时,并不能自动转换,需要指定依赖关系。

在 AutoMapper 注解中,可以通过属性 useEnums 来指定,当前转换关系,需要依赖的枚举类列表。这些枚举需要被 AutoEnumMapper注解。

该特性从 1.4.2 开始支持

需要注意的是,当两个类在同一个模块(module)中,无需指定,可以自动转换。当前特性主要解决跨模块之间不能自动转换的问题。

六、一个类与多个类之间的转换

MapStruct Plus 还支持一个类和多个类进行转换,可以通过 @AutoMappers 来配置,该注解支持配置多个 @AutoMapper。

在配置多个类进行转化的时候,多个类可能有相同的属性,为了解决属性冲突的问题,可以使用 @AutoMappings 指定多个转换规则,并且在使用 @AutoMapping 注解时,配置 targetClass 属性,指定当前规则的目标转化类。

如果配置 @AutoMapping 注解时,没有指定 targetClass,那么当前规则就会应用所有类转换。

1. 定义对象

MapStructPlus 除了支持一个类与单个目标类型进行转换,还支持一个类与多个目标类型进行转换。

配置多个类转换

当想要配置一个类与多个类进行转换时,可以通过 @AutoMappers 来配置,该注解支持配置多个 @AutoMapper

定义一个User类,一个Course类,一个UserDTO类。其中UserDTO类将与User类和Course类互相映射。User类和Course类都有username属性,但是只将User类中的username属性映射。

User类:

@Data

@AutoMapper(target = UserDTO.class, uses = StringToListConverter.class)

public class User {



    private String username;



    private int age;



    private boolean young;



    @AutoMapping(target = "tagList")

    private String tags;

}

Course类:

@Data

@AutoMapper(target = UserDTO.class)

public class Course {



    @AutoMapping(targetClass = UserDTO.class, ignore = true) // 忽略 UserDTO 中的 username 属性

    private String username;



    private String teacher;

}

UserDTO类:

@Data

@AutoMappers({

        @AutoMapper(target = User.class, uses = ListToStringConverter.class),

        @AutoMapper(target = Course.class)

})

public class UserDTO {



    @AutoMappings({

            @AutoMapping(targetClass = User.class),

            @AutoMapping(targetClass = Course.class, ignore = true)

    })

    private String username;



    private int age;



    private boolean young;



    @AutoMapping(targetClass = User.class, target = "tags")

    private List<String> tagList;



    private String teacher;

}

2. 转换测试

@SpringBootTest

public class OneToOthersTest {



    @Autowired

    private Converter converter;



    @Test

    void testOneToMany() {

        // 创建 User 对象

        User user = new User();

        user.setUsername("jack");

        user.setAge(25);

        user.setYoung(false);

        user.setTags("Java,Python,Go,C++");



        // 创建 Course 对象

        Course course = new Course();

        course.setUsername("Java 开发");

        course.setTeacher("教 Java 的老师");



        // 转换(User 对象和 Course 对象)为 UserDTO 对象

        UserDTO userDTO = converter.convert(user, UserDTO.class);

        userDTO = converter.convert(course, userDTO);

        System.out.println(userDTO);



        // 转换 UserDTO 对象为(User 对象和 Course 对象)

        user = converter.convert(userDTO, User.class);

        course = converter.convert(userDTO, Course.class);

        System.out.println(user);

        System.out.println(course);

    }



}

3. 测试结果

### 使用 AutoGPTQ 库量化 Transformer 模型 为了使用 `AutoGPTQ` 对 Transformer 模型进行量化,可以遵循如下方法: 安装所需的依赖包是必要的操作。通过 pip 安装 `auto-gptq` 可以获取最新版本的库。 ```bash pip install auto-gptq ``` 加载预训练模型并应用 GPTQ (General-Purpose Tensor Quantization) 技术来减少模型大小和加速推理过程是一个常见的流程。下面展示了如何利用 `AutoGPTQForCausalLM` 类来进行这一工作[^1]。 ```python from transformers import AutoModelForCausalLM, AutoTokenizer from auto_gptq import AutoGPTQForCausalLM model_name_or_path = "facebook/opt-350m" quantized_model_dir = "./quantized_model" tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) model = AutoModelForCausalLM.from_pretrained(model_name_or_path) # 加载已经量化的模型或者创建一个新的量化器对象用于量化未压缩过的模型 gptq_model = AutoGPTQForCausalLM.from_pretrained(quantized_model_dir, model=model, tokenizer=tokenizer) ``` 对于那些希望进一步优化其部署环境中的模型性能的人来说,`AutoGPTQ` 提供了多种配置选项来自定义量化参数,比如位宽(bit-width),这有助于平衡精度损失与运行效率之间的关系。 #### 注意事项 当处理特定硬件平台上的部署时,建议查阅官方文档以获得最佳实践指导和支持信息。此外,在实际应用场景之前应该充分测试经过量化的模型以确保满足预期的质量标准。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值