MapStruct是一个代码生成器,它基于约定优于配置的原则,使用Java注解来简化从源对象到目标对象的映射过程。它主要用于减少样板代码,提高开发效率,并且通过编译时代码生成来保证性能。
我的个人实践方面是在2021年前那时候在项目中使用dozer较多,后来发现MapStruct处理对象映射属性复制更灵活方便,就直接转向使用MapStruct。
学习MapStruct的用法,最简单高效就是去阅读官网的文档:MapStruct – Java bean mappings, the easy way!以及下载官方的示例代码:GitHub - mapstruct/mapstruct-examples: Examples for using MapStruct
各种用法还是比较详细的
目录
1. java对象基本属性映射
2.设置属性的默认值
3.使用条件表达式
4.映射集合类属性
5.使用自定义映射方法
6.继承和多态
7. 使用组件模型
8.忽略默认值
9.MapStruct的注解
10.使用 @Mapper 注解定义映射器
11.映射多个源对象到一个目标对象
12. 映射一个源对象到多个目标对象
13.SpringBoot项目整合使用MapStruct
以下是MapStruct的一些常见用法和相应的代码示例:
1. java对象基本属性映射
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "address", source = "personAddress")
PersonDto personToPersonDto(Person person);
}
class Person {
private String name;
private Address personAddress;
// getters and setters
}
class PersonDto {
private String name;
private AddressDto address;
// getters and setters
}
class Address {
private String street;
// getters and setters
}
class AddressDto {
private String street;
// getters and setters
}
如果有多个属性值的名称不一样,则需要使用@Mappings注解来使用多个 @Mapping来制定属性映射关系
比如:
@Mapper
public interface MyMapper {
@Mappings({
@Mapping(target = "name", source = "fullName"),
@Mapping(target = "age", source = "years")
})
void updateTargetFromSource(Source source,Target target);
}
2.设置属性的默认值
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "email", expression = "java(email != null ? email : 'default@email.com')")
UserDto userToUserDto(User user);
}
class User {
private String name;
private String email;
// getters and setters
}
class UserDto {
private String name;
private String email;
// getters and setters
}
3.使用条件表达式
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(target = "status", expression = "java(order.isShipped() ? \"SHIPPED\" : \"PENDING\")")
OrderDto orderToOrderDto(Order order);
}
class Order {
private boolean shipped;
// getters and setters
}
class OrderDto {
private String status;
// getters and setters
}
4.映射集合类属性
@Mapper
public interface ItemMapper {
ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);
@Mapping(target = "items", expression = "java(mapItemsToItemDtos(order.getItems()))")
OrderDto orderToOrderDto(Order order);
List<ItemDto> mapItemsToItemDtos(List<Item> items);
}
class Order {
private List<Item> items;
// getters and setters
}
class OrderDto {
private List<ItemDto> items;
// getters and setters
}
class Item {
// properties and methods
}
class ItemDto {
// properties and methods
}
5.使用自定义映射方法
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping("licensePlate", ignore = true)
CarDto carToCarDto(@MappingTarget CarDto target, Car car);
void setLicensePlateIfPresent(@MappingTarget CarDto target, Car car);
}
class Car {
private String make;
private String licensePlate;
// getters and setters
}
class CarDto {
private String make;
// getters and setters
}
// 在项目编译后MapStruct会自动在映射器实现类中实现该方法
void setLicensePlateIfPresent(CarDto target, Car car) {
if (car.getLicensePlate() != null && !car.getLicensePlate().isEmpty()) {
target.setLicensePlate(car.getLicensePlate());
}
}
@MappingTarget
是 MapStruct 中一个重要的注解,用于标识目标对象。在使用 MapStruct 进行对象映射时,有时我们需要将映射结果更新到一个已经存在的实例中,而不是创建一个新的对象。@MappingTarget
就是为此目的而设计的。
或者你可以直接在映射接口中实现一个默认的方法,来自定义映射关系
public interface MyMapper {
// 其他映射方法
// 自定义映射逻辑的默认方法
void customMapping(@MappingTarget MyTarget target, MySource source) {
if (source.getCondition()) {
target.setSomeProperty("valueIfConditionTrue");
} else {
target.setSomeProperty("valueIfConditionFalse");
}
}
}
或者直接调用另外的映射方法做属性映射:
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.example.mapper;
import java.lang.annotation.Target;
import java.util.Map;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.example.dto.Department;
import org.mapstruct.example.dto.Employee;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface MapToBeanMapper {
MapToBeanMapper INSTANCE = Mappers.getMapper(MapToBeanMapper.class);
@Mapping(target = "department", ignore = true)
Employee fromMap(Map<String, String> map);
@AfterMapping
default void finishEmployee(@MappingTarget Employee employee, Map<String, String> map) {
employee.setDepartment(fromMapToDepartment(map));
}
@Mapping(target = "id", source = "did")
@Mapping(target = "name", source = "dname")
Department fromMapToDepartment(Map<String, String> map);
}
这段代码通过 MapStruct 提供了一个完整的映射解决方案,用于将 Map<String, String>
转换为 Employee
对象,并通过一个 @AfterMapping
方法在映射完成后手动设置 Employee
对象的 department
字段。
6.继承和多态
@Mapper
public interface AnimalMapper {
AnimalMapper INSTANCE = Mappers.getMapper(AnimalMapper.class);
AnimalDto animalToAnimalDto(Animal animal);
}
class Animal {
private String name;
// getters and setters
}
class Cat extends Animal {
private String favoriteToy;
// getters and setters
}
class AnimalDto {
private String name;
// getters and setters
}
class CatDto extends AnimalDto {
private String favoriteToy;
// getters and setters
}
MapStruct会自动处理继承关系,将子类的属性也映射到相应的DTO中。
7. 使用组件模型
MapStruct允许开发者定义组件,这些组件可以包含自定义的映射逻辑,可以被多个映射器使用。
@Mapper(componentModel = "spring",uses=DepartmentNameMapper.class)
public interface EmployeeMapper {
EmployeeMapper INSTANCE = Mappers.getMapper(EmployeeMapper.class);
@Mapping(target = "departmentName", source = "department.name")
EmployeeDto employeeToEmployeeDto(Employee employee);
}
@Component
public class DepartmentNameMapper {
public String toDepartmentName(Department department) {
return department.getName();
}
}
在这个例子中,DepartmentNameMapper
是一个Spring组件,它提供了一个方法来获取部门的名称,这个方法可以在映射过程中被使用。
这些只是MapStruct的一些基本用法。MapStruct非常灵活,支持许多高级特性,如映射多个源对象到一个目标对象、使用自定义的映射方法、处理复杂的数据结构等
8.忽略默认值
在映射过程中,你可能希望忽略源对象中的默认值,只复制非默认值的属性。
@Mapper
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(target = "price", ignore = true)
ProductDto productToProductDto(Product product);
}
class Product {
private double price = 0.0; // 默认值
// 其他属性和getter/setter
}
class ProductDto {
private double price;
// getter/setter
}
在这个例子中,ProductDto
的 price
字段将不会被 Product
对象的默认值 0.0
填充
9.MapStruct的注解
MapStruct 允许你使用 @Mapping
注解来指定映射关系,包括如何从源属性映射到目标属性。
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "fullName", expression = "java(user.getFirstName() + ' ' + user.getLastName())")
UserDto userToUserDto(User user);
}
class User {
private String firstName;
private String lastName;
// getters and setters
}
class UserDto {
private String fullName;
// getter/setter
}
在这个例子中,UserDto
的 fullName
属性是通过连接 User
对象的 firstName
和 lastName
属性来生成的。
使用@MappingTarget可以自定义源对象到目标对象的属性的映射关系;
使用 @AfterMapping
或 @BeforeMapping
注解结合 @MappingTarget
来在映射前后执行自定义逻辑。上边的有结合 @AfterMapping的示例
与@AfterMapping
相对应,MapStruct还提供了@BeforeMapping
注解,用于在映射开始之前执行自定义逻辑。
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@BeforeMapping
default void beforeMapping(@MappingTarget UserDto target, User source) {
// 执行映射前的自定义逻辑,例如初始化或验证源对象
if (source == null) {
throw new IllegalArgumentException("Source user cannot be null");
}
}
UserDto userToUserDto(User user);
}
10.使用 @Mapper
注解定义映射器
@Mapper
注解用于定义映射器接口,你可以通过这个注解来配置映射器的行为,从而使用其他的自定义的映射器
@Mapper(uses = {DateMapper.class, EnumMapper.class})
public interface MyCustomMapper {
// 映射方法
}
@Mapper
public abstract class DateMapper {
@Mapping(target = "dateAsString", expression = "java(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE.format(source.getDate()))")
abstract void mapDate(@MappingTarget MyTarget target, MySource source);
}
@Mapper
public abstract class EnumMapper {
// 映射枚举类型的逻辑
}
在这个例子中,MyCustomMapper
使用了 uses
属性来指定其他映射器类,这些类提供了额外的映射逻辑。
示例二
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.example.mapper;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.example.dto.Customer;
import org.mapstruct.example.dto.CustomerDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(uses = { OrderItemMapper.class })
public interface CustomerMapper {
CustomerMapper MAPPER = Mappers.getMapper( CustomerMapper.class );
@Mapping(source = "orders", target = "orderItems")
@Mapping(source = "customerName", target = "name")
Customer toCustomer(CustomerDto customerDto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.example.mapper;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.example.dto.OrderItem;
import org.mapstruct.example.dto.OrderItemDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface OrderItemMapper {
OrderItemMapper MAPPER = Mappers.getMapper(OrderItemMapper.class);
OrderItem toOrder(OrderItemDto orderItemDto);
@InheritInverseConfiguration
OrderItemDto fromOrder(OrderItem orderItem);
}
@InheritInverseConfiguration
是 MapStruct 中的一个注解,用于继承已有映射方法的逆向配置。这在需要定义双向映射(从一个类到另一个类,然后从另一个类再映射回第一个类)时非常有用,可以减少代码重复,提高代码的可维护性。
11.使用 @Named
注解
在mapstruct的源码中@Named注解类就有对应的示例:
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks mapping methods with the given qualifier name. Can be used to qualify a single method or all methods of a given
* type by specifying this annotation on the type level.
*
* Will be used to select the correct mapping methods when mapping a bean property type, element of an iterable type
* or the key/value of a map type.
*
* Example (both methods of {@code Titles} are capable to convert a string, but the ambiguity is resolved by applying
* the qualifiers in {@code @Mapping}:
*
*
* @Named("TitleTranslator")
* public class Titles {
*
* @Named("EnglishToGerman")
* public String translateTitleEG(String title) {
* // some mapping logic
* }
*
* @Named("GermanToEnglish")
* public String translateTitleGE(String title) {
* // some mapping logic
* }
* }
*
* @Mapper( uses = Titles.class )
* public interface MovieMapper {
*
* @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } )
* GermanRelease toGerman( OriginalRelease movies );
*
* }
*
* The following implementation of {@code MovieMapper} will be generated:
*
*
* public class MovieMapperImpl implements MovieMapper {
* private final Titles titles = new Titles();
*
* @Override
* public GermanRelease toGerman(OriginalRelease movies) {
* if ( movies == null ) {
* return null;
* }
*
* GermanRelease germanRelease = new GermanRelease();
*
* germanRelease.setTitle( titles.translateTitleEG( movies.getTitle() ) );
*
* return germanRelease;
* }
* }
*
*
* @author Sjaak Derksen
* @see org.mapstruct.Mapping#qualifiedByName()
* @see IterableMapping#qualifiedByName()
* @see MapMapping#keyQualifiedByName()
* @see MapMapping#valueQualifiedByName()
*/
@Target( { ElementType.TYPE, ElementType.METHOD } )
@Retention( RetentionPolicy.CLASS )
@Qualifier
public @interface Named {
/**
* A name qualifying the annotated element
*
* @return the name.
*/
String value();
}
在 MapStruct 中,@Named
注解主要用于自定义方法,以便在映射器中引用这些方法。通过给这些方法加上 @Named
注解,并在 @Mapping
注解中引用这些方法,可以实现复杂的映射逻辑。MapStruct 支持 JSR-330 的 @Named
注解,这使得定义和使用自定义的转换逻辑更加灵活和清晰。
使用@Named注解定义自定义方法
假设我们有一个自定义的方法需要在映射过程中使用,我们可以使用 @Named
注解为该方法命名,并在 @Mapping
注解中引用它。
示例
假设我们有以下两个类:
public class Source {
private String name;
private String date;
// getters and setters
}
public class Target {
private String fullName;
private LocalDate birthDate;
// getters and setters
}
我们想要将 Source
的 name
映射到 Target
的 fullName
,并将 date
字符串转换为 LocalDate
并映射到 birthDate
。为此,我们可以定义一个自定义的日期转换方法。
定义映射器接口和自定义方法
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Mapper
public interface SourceToTargetMapper {
SourceToTargetMapper INSTANCE = Mappers.getMapper(SourceToTargetMapper .class);
@Mapping(source = "name", target = "fullName")
@Mapping(source = "date", target = "birthDate", qualifiedByName = "stringToLocalDate")
Target sourceToTarget(Source source);
@Named("stringToLocalDate")
default LocalDate stringToLocalDate(String date) {
return LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
测试使用映射器
public class TestMapping {
public static void main(String[] args) {
Source source = new Source();
source.setName("John Doe");
source.setDate("1980-01-01");
Target target = SourceToTargetMapper.INSTANCE.sourceToTarget(source);
System.out.println("Full Name: " + target.getFullName()); // 输出: John Doe
System.out.println("Birth Date: " + target.getBirthDate()); // 输出: 1980-01-01
}
}
11.映射多个源对象到一个目标对象
MapStruct允许你映射多个源对象到一个目标对象;这对于需要将来自不同源的数据合并到一个目标对象的情况非常有用。MapStruct 提供了灵活的注解配置,使得这种映射变得直观和易于实现。
示例:假设我们有以下两个源对象 Source1
和 Source2
,以及一个目标对象 Target
:
public class Source1 {
private String name;
private String email;
// getters and setters
}
public class Source2 {
private String address;
private String phoneNumber;
// getters and setters
}
public class Target {
private String name;
private String email;
private String address;
private String phoneNumber;
// getters and setters
}
我们希望将 Source1
和 Source2
的属性合并到 Target
对象中
定义映射器接口
我们需要在映射器接口中定义一个方法,该方法接受多个源对象并返回目标对象。
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MyMapper {
MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);
@Mapping(source = "source1.name", target = "name")
@Mapping(source = "source1.email", target = "email")
@Mapping(source = "source2.address", target = "address")
@Mapping(source = "source2.phoneNumber", target = "phoneNumber")
Target map(Source1 source1, Source2 source2);
}
由于两个源对象和目标对象中的属性名称一致,所以也可以省略掉方法上的四个@Mapping
测试类
public class TestMapping {
public static void main(String[] args) {
Source1 source1 = new Source1();
source1.setName("John Doe");
source1.setEmail("john.doe@example.com");
Source2 source2 = new Source2();
source2.setAddress("123 Main St");
source2.setPhoneNumber("555-1234");
Target target = MyMapper.INSTANCE.map(source1, source2);
System.out.println("Name: " + target.getName()); // 输出: John Doe
System.out.println("Email: " + target.getEmail()); // 输出: john.doe@example.com
System.out.println("Address: " + target.getAddress()); // 输出: 123 Main St
System.out.println("Phone Number: " + target.getPhoneNumber()); // 输出: 555-1234
}
}
12. 映射一个源对象到多个目标对象
使用 @MappingTarget
注解,可以一次映射到多个目标对象。
示例:
假设我们有一个源对象 Source
和两个目标对象 Target1
和 Target2
,我们希望将 Source
对象的一部分属性映射到 Target1
,另一部分属性映射到 Target2
。
public class Source {
private String name;
private String email;
private String address;
private String phoneNumber;
// getters and setters
}
public class Target1 {
private String name;
private String email;
// getters and setters
}
public class Target2 {
private String address;
private String phoneNumber;
// getters and setters
}
定义映射接口
我们需要在映射器接口中定义一个方法,该方法接受 Source
对象和两个目标对象,并将 Source
对象的属性映射到相应的目标对象中。
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MyMapper {
MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);
void toMultiTargets(Source source, @MappingTarget Target1 target1, @MappingTarget Target2 target2);
}
或者也可以自定义辅助方法实现具体的映射逻辑
@Mapper
public interface MyMapper {
MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);
void toMultiTargets(Source source, @MappingTarget Target1 target1, @MappingTarget Target2 target2);
default void toMultiTargets(Source source, @MappingTarget Target1 target1, @MappingTarget Target2 target2) {
if (source != null) {
target1.setName(source.getName());
target1.setEmail(source.getEmail());
target2.setAddress(source.getAddress());
target2.setPhoneNumber(source.getPhoneNumber());
}
}
}
测试类:
public class TestMapping {
public static void main(String[] args) {
Source source = new Source();
source.setName("John Doe");
source.setEmail("john.doe@example.com");
source.setAddress("123 Main St");
source.setPhoneNumber("555-1234");
Target1 target1 = new Target1();
Target2 target2 = new Target2();
MyMapper.INSTANCE.toMultiTargets(source, target1, target2);
System.out.println("Target1 Name: " + target1.getName()); // 输出: John Doe
System.out.println("Target1 Email: " + target1.getEmail()); // 输出: john.doe@example.com
System.out.println("Target2 Address: " + target2.getAddress()); // 输出: 123 Main St
System.out.println("Target2 Phone Number: " + target2.getPhoneNumber()); // 输出: 555-1234
}
}
13.SpringBoot项目整合使用MapStruct
在Spring Boot项目中集成MapStruct主要涉及几个步骤:添加依赖、配置Mapper组件、使用MapStruct注解以及享受自动映射带来的便利。下面是详细的步骤和用法示例:
添加依赖
首先,需要在Spring Boot项目的pom.xml
(对于Maven项目)或build.gradle
(对于Gradle项目)中添加MapStruct的依赖。
Maven:
<dependencies>
<!-- MapStruct依赖 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.2.Final</version>
</dependency>
<!-- 处理器依赖,用于生成映射代码 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.2.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
Gradle
dependencies {
// MapStruct依赖
implementation 'org.mapstruct:mapstruct:1.5.2.Final'
// 处理器依赖,用于生成映射代码
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
}
2.配置Mapper组件
package com.example.demo.mapper;
import org.mapstruct.Mapper;
import org.springframework.stereotype.Component;
@Mapper(componentModel = "spring")
public interface MyEntityMapper {
MyDto entityToDto(MyEntity entity);
@Mapping(target = "createdDate", expression = "java(java.time.LocalDate.now())")
MyDto entityToDtoWithDefaultDate(MyEntity entity);
}
在Spring组件中,通过自动装配MapStruct Mapper接口来实现对象之间的自动映射。
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.mapper.MyEntityMapper;
import com.example.demo.entity.MyEntity;
import com.example.demo.dto.MyDto;
@Service
public class MyService {
private final MyEntityMapper myEntityMapper;
@Autowired
public MyService(MyEntityMapper myEntityMapper) {
this.myEntityMapper = myEntityMapper;
}
public MyDto convert(MyEntity entity) {
return myEntityMapper.entityToDto(entity);
}
}
确保Spring Boot的启动类或配置类中包含了@EnableAutoConfiguration
或@SpringBootApplication
注解,这样Spring Boot就能自动配置应用程序,包括MapStruct的Mapper组件。
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
更多用法可以详细参照官方的example示例代码,分类挺详细的: