在Java应用程序开发中,您可能需要将一个对象映射到另一个对象。例如,在处理输入数据时,您可能需要将DTO(数据传输对象)转换为实体类,或者在将数据从实体类保存到数据库时,您可能需要将实体类转换为DTO。在这些情况下,您可以使用Java BeanUtils库来执行对象映射。然而,近年来,Mapstruct正在成为一个更流行的解决方案。本文将阐述Mapstruct与BeanUtils之间的区别,并解释为什么Mapstruct更适合Java对象映射。编辑
是什么
-
Mapstruct简介 Mapstruct是一个注解处理器,它可以根据注释生成类型安全的Java Bean映射代码。它提供了一种简单、快速且类型安全的方法来转换Java对象。它支持多种映射策略,包括注释、名称约定和自定义映射方法等。
Mapstruct官网是这样说的:MapStruct 是一种代码生成器,它基于约定重于配置的方法,大大简化了 Java Bean 类型之间映射的实现。
就是处理对象之间的属性拷贝,利用Java注解将一个实体类的属性安全地赋值给另一个实体类。
-
BeanUtils简介 BeanUtils是Apache Commons项目的一部分,它提供了一组用于复制Java Bean属性的实用程序方法。它可以将一个Java Bean的属性值复制到另一个Java Bean中,也可以将Map中的键值对映射到Java Bean属性中。
-
Mapstruct与BeanUtils比较 虽然Mapstruct和BeanUtils都是用于Java对象映射的库,但它们之间存在一些区别。
类型安全 Mapstruct提供了类型安全的映射代码生成。换句话说,如果您在源对象和目标对象之间存在编译时错误,那么编译器将在编译时捕获这些错误。这样可以在编写代码时更容易地检测到错误,并且可以减少运行时错误的风险。BeanUtils没有提供这样的类型安全。
性能 由于Mapstruct是一个注释处理器,它会在编译时生成映射代码。这意味着Mapstruct可以在运行时非常快速地执行对象映射,因为它不需要在运行时进行反射调用。相比之下,BeanUtils在运行时使用反射来执行属性复制,这可能会导致性能下降。
映射策略 Mapstruct支持多种映射策略,包括注释、名称约定和自定义映射方法等。这些映射策略允许您以多种方式指定源对象和目标对象之间的映射关系。相比之下,BeanUtils只支持基于属性名称的映射策略,这意味着源对象和目标对象中的属性名称必须匹配。
怎么使用:
您需要添加以下依赖项到您的Maven或Gradle构建文件中
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
创建Mapstruct接口,加上@Mapper注解这个是Mapstruct包下的注解,标注这个注解后会在编译期生成该接口的实现类对象。
@Mapper注解的componentModel属性有五种赋值:介绍常用的两种:
DEFAULT:默认,不使用任何组建类型,可以通过Mappers.getMapper(Class) 方式获取实例对象
SPRING:在实现类上注解 @Component,可通过 @Autowired 方式注入。
下面是我写的一个demo,以及生成的实现类
UserDto.class
@Data
public class UserDTO {
private Long id;
private String name;
private String password;
private BigDecimal price;
}
UserVo.class
@Data
public class UserVO {
private Long id;
private String price;
private String username;
private String password;
private Integer age;
private Boolean bald;
private List<String> roles;
private String localDate;
private PersonVO personVO;
}
PersonVo
@Data
public class PersonVO {
private Integer id;
private Integer age;
private String name;
private LocalDate createTime;
}
UserMap.class
ps:这个接口可能比较乱,但是基本包含常用的一些功能,可以自己试试方便理解
@Mapper(uses = {NameD.class})
public interface UserMap {
//循环调用的场景下提高性能的关键,通过UserMap.INSTANCE获取实例
//类加载器获取实例对象
UserMap INSTANCE = Mappers.getMapper(UserMap.class);
//定义抽象方法
@Mapping(source = "name",target = "username")
@Mapping(source = "price",target = "price",numberFormat = "¥#.00")
@Mapping(source = "localDate",target = "localDate",dateFormat = "MM")
@Mapping(source = "personDTO",target = "personVO")
UserVO userDTOToVO(UserDTO userDTO);
@Mapping(target = "createTime",source = "birthday",qualifiedByName = "Str2Date")
PersonVO toPersonVO(PersonDTO personVO);
}
UserMap.class 编译之后会生成实现类,相信根据编译后的代码,就可以明白上面各注解的用途,让你的项目在你老大面前眼前一亮
编辑
UserMapImpl.class
public class UserMapImpl implements UserMap {
private final NameD nameD = new NameD();
private final DateTimeFormatter dateTimeFormatter_MM_12464 = DateTimeFormatter.ofPattern("MM");
public UserMapImpl() {
}
public UserVO userDTOToVO(UserDTO userDTO) {
if (userDTO == null) {
return null;
} else {
UserVO userVO = new UserVO();
userVO.setUsername(userDTO.getName());
if (userDTO.getPrice() != null) {
userVO.setPrice(this.createDecimalFormat("¥#.00").format(userDTO.getPrice()));
}
if (userDTO.getLocalDate() != null) {
userVO.setLocalDate(this.dateTimeFormatter_MM_12464.format(userDTO.getLocalDate()));
}
userVO.setPersonVO(this.toPersonVO(userDTO.getPersonDTO()));
userVO.setId(userDTO.getId());
userVO.setPassword(userDTO.getPassword());
userVO.setAge(userDTO.getAge());
userVO.setBald(userDTO.getBald());
List<String> list = userDTO.getRoles();
if (list != null) {
userVO.setRoles(new ArrayList(list));
}
return userVO;
}
}
public PersonVO toPersonVO(PersonDTO personVO) {
if (personVO == null) {
return null;
} else {
PersonVO personVO1 = new PersonVO();
personVO1.setCreateTime(this.nameD.String2LocalDateTime(personVO.getBirthday()));
personVO1.setAge(personVO.getAge());
personVO1.setName(personVO.getName());
return personVO1;
}
}
private DecimalFormat createDecimalFormat(String numberFormat) {
DecimalFormat df = new DecimalFormat(numberFormat);
df.setParseBigDecimal(true);
return df;
}
}
@Mapping()
source = "name"被拷贝对象属性名称
target = "username"待赋值对象属性名称
可能看起来比较繁琐,如果命名规范,则不需要额外设置
包扩对象之间的拷贝,会自动调用方法注入
@Mapping(target = "createTime",source = "birthday",qualifiedByName = "Str2Date")
PersonVO toPersonVO(PersonDTO personVO);
private final NameD nameD = new NameD();
这是定义的string转LocalDate的一个方法,也可以通过注解的方式预编译
NameD.class 自定义时间格式转换器
import org.mapstruct.Named;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Named("NameD")
public class NameD {
@Named("Str2Date")
public LocalDate String2LocalDateTime(String s){
LocalDate localDateTime = LocalDate.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return localDateTime;
}
}
最后还有一种默认表达式用法:
当使用Mapstruct进行对象映射时,可以使用默认表达式来定义属性之间的映射关系。默认表达式允许您在源和目标属性之间建立一种默认的映射规则,以便在没有特定映射方法或注释的情况下进行映射。
以下是Mapstruct中默认表达式的用法示例:
-
在Mapper接口中定义默认方法:
@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); @Mapping(target = "manufacturer", source = "make", defaultExpression ="java("Unknown")") CarDto toCarDto(Car car); }
在上面的示例中,我们定义了一个名为toCarDto()的映射方法,并使用了@Mapping注解。在注解中,我们使用了defaultExpression属性来定义默认表达式。在这个例子中,如果make属性为空,那么manufacturer属性将默认设置为"Unknown"。
-
使用自定义方法作为默认表达式:
@Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class); @Mapping(target = "manufacturer", source = "make", defaultExpression = "java(com.example.CarMapperUtils.getDefaultManufacturer(car.getMake()))") CarDto toCarDto(Car car); }
在上面的示例中,我们使用一个自定义的静态方法CarMapperUtils.getDefaultManufacturer()作为默认表达式。该方法接收car.getMake()作为参数,并返回一个默认的manufacturer字符串。
通过使用默认表达式,您可以在没有特定映射方法或注释的情况下定义属性之间的默认映射规则。这为您提供了更大的灵活性和控制权,使得对象映射更加便捷和简单。
Mapstruct是一个用于Java对象映射的库,提供了类型安全、高性能和多种映射策略等优点。相比之下,BeanUtils则没有这些优点。如果您正在进行Java对象映射,并且需要更好的性能和更好的类型安全性,请考虑使用Mapstruct。