快丢掉BeanUtils吧,MapStruct才是YYDS

MapStruct是一个代码生成器,简化了Java对象之间的映射。它避免了手动属性映射的繁琐工作,能够在编译时发现错误,提供比BeanUtils更好的性能。通过注解配置,可以处理同名、不同名字段映射,自定义对象和转换逻辑,以及集合映射。@BeforeMapping和@AfterMapping用于转换前后处理。MapStruct在后端开发中能有效提高代码质量和效率。

场景

通常,在后端项目开发中,因为有项目分层的设计,例如MVC架构,以及最近很火热的DDD架构中,会在不同的层级,有对应的DO,BO,VO,DTO等各种各样的POJO类,而我们在层级之间进行调用的数据传递时,通常要进行对象属性之间的映射。对于一些简单的对象,可能会直接使用get,set方法完成,或者使用BeanUtils工具类来完成属性之间的映射。

这些代码往往是枯燥、无聊的,并且在不同的业务处理类中可能需要重复地对两个对象进行互相转换。导致代码里充斥着大量的get,set转换,如果使用BeanUtils,可能会因为字段名称不一致,导致在运行时才能发现问题。

那有没有什么方案能解决这个问题呢?

答案就是使用MapStruct,可以优雅地解决上面的这些问题。

MapStruct是一种代码生成器组件,它遵循约定优于配置的原则,可以让我们的Bean对象之间的转换变得更简单。

为什么要使用MapStruct?

如前文中描述,在多层应用设计中,需要在不同的对象模型之间进行转换,属性映射,手动编写这些代码不仅繁琐,而且很容易出错,MapStruct的目的是让这项工作变得简单,自动化。

相比其他的映射框架,比如BeanUtils,或者Json序列化反序列化等方式,MapStruct能在编译时就生成映射,确保程序运行性能,并且能在编译时就发现错误。

MapStruct怎么用?

MapStruct本质上是一个注解处理器,可以直接在Maven或Gradle等编译工具中集成。

以Maven为例,我们需要先在依赖中添加MapStruct依赖,并将mapstruct-processor配置在maven插件中。

接下来,便可以在代码中使用MapStruct。

同名字段映射

比如,我们现在有一个功能是要从持久层查询学生对象Student,

创建和生成映射的实现。

componentModel = "spring":该属性的含义是将StudentMapper的实例作为Spring的Bean对象,放在Spring容器中,这样就可以在其他业务代码中方便的注入。

因为Student和StudentDTO的属性名相同,所以我们不需要任何其他代码显式映射。

不同名字段映射

假如DTO和PO之间的字段名称不同,应该如何处理呢?

Student.java

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
}

StudentDTO.java

@Data
public class StudentDTO {
    private int no;
    private String name;
    private String sex;
}

如上代码所示,在Student和StudentDTO中,性别字段的名称不一致。要实现这种情况的映射,只需要添加如下的@Mapping注解。

@Mapper(componentModel = "spring")
public interface StudentMapper {
     @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}

自定义对象属性映射

假如每个学生有自己的地址信息Address,那么该如何处理呢?

源对象类

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}

目标对象类

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private AddressDTO address;
}

@Data
public class AddressDTO{
    private String city;
    private String province;
}

这种要对内部对象进行映射,我们需要对内部对象也创建一个Mapper,然后将内部对象的Mapper导入到StudentMapper中。

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressDTO toDto(Address address);
}

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);
}

自定义转换逻辑映射

如果在对象转换时,不仅是简单的属性之间的映射,需要按照某种业务逻辑进行转换,比如每个Student中的地址信息Address,在StudentDTO中只需要地址信息中的city。

源对象类

@Data
public class Student{
    private int no;
    private String name;
    private String gender;
    private Address address;
}

@Data
public class Address{
    private String city;
    private String province;
}

目标对象类

@Data
public class StudentDTO{
    private int no;
    private String name;
    private String sex;
    private String city;
}

针对这种情况,我们可以直接在source中使用address.city,也可以通过自定义方法来完成逻辑转换。

@Mapper(componentModel = "spring",uses = {AddressMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    // @Mapping(source = "address.city", target = "city")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    StudentDTO toDto(Student student);

    @Named("getAddressCity")
    default String getChildCircuits(Address address) {
        if(address == null) {
            return "";
        }
        return address.getCity();
    }
}

集合映射

使用MapStruct进行集合字段的映射也很简单。比如每个Student中有多门选修的课程List<Course>,要映射到StudentDTO中的List<CourseDTO>中。

@Mapper(componentModel = "spring")
public interface CourseMapper {
    CourseDTO toDto(Course port);

    List<CourseDTO> toCourseDtoList(List<Course> courses);
}

@Mapper(componentModel = "spring",uses = {CourseMapper.class})
public interface StudentMapper {
    @Mapping(source = "gender", target = "sex")
    @Mapping(source = "address", target = "city",qualifiedByName = "getAddressCity")
    CircuitDto toDto(Circuit circuit);
}

其他特殊情况映射

除了常见的对象映射外,有些情况我们可能需要在转换时设置一些固定值,假设StudentDTO中有学历字段degree,但是暂时该数据还未录入,所以这里要设置默认值“未知”。可以使用@Mapping注解完成。

@Mapping(target = "degree", constant = "未知")

@BeforeMapping和@AfterMapping

@BeforeMapping@AfterMapping是两个很重要的注解,看名字基本就可以猜到,可以用来在转换之前和之后做一些处理。

比如我们想要在转换之前做一些数据验证,集合初始化等功能,可以使用@BeforeMapping

想要在转换完成之后进行一些数据结果的修改,比如根据更加StudentDTO中选修课程List<CourseDTO>的数量来给是否有选修课字段haveCourse设置布尔值等。

@Mapper(componentModel = "spring",uses = {PortMapper.class})
public interface StudentMapper {
    @BeforeMapping
    default void setCourses(Student student) {
        if(student.getCourses() == null){
            student.setCourses(new ArrayList<Course>());
        }
    }

    @Mapping(source = "gender", target = "sex")
    StudentDTO toDto(Student student);

    @AfterMapping
    default void setHaveCourse(StudentDTO studentDto) {
        if(studentDto.getCourses()!=null && studentDto.getCourses() >0){
            studentDto.setHaveCourse(true);
        }
    } 
}

总结

在本文中介绍了如何使用MapStruct来优雅的实现对象属性映射,减少我们代码中的get,set代码,避免对象属性之间映射的错误。在以上示例代码中可以看出,MapStruct大量使用了注解,让我们可以轻松完成对象映射。


 

 

<think>嗯,用户想对比分析MapStructBeanUtils的优缺点以及适用场景,特别是性能、功能和使用场景这几个方面。首先,我需要回顾一下之前提供的引用内容,里面提到了两者的技术原理、性能、类型安全、配置灵活性等方面的差异。 首先,技术原理方面,MapStruct是基于JSR 269的注释处理器,在编译时生成代码,而BeanUtils用的是反射机制。这意味着MapStruct在运行时没有反射开销,性能应该更好,尤其是处理大量数据的时候。不过,用户可能想知道具体性能差距有多大,或者在不同场景下的表现差异。 然后是功能方面,引用里提到MapStruct支持复杂的映射,比如嵌套对象、集合和自定义方法,而BeanUtils只能处理简单的属性复制。需要具体说明这些功能差异,比如MapStruct如何处理不同字段名或格式转换的情况,而BeanUtils可能需要在代码中手动处理这些。 使用场景的话,用户可能想知道什么时候应该选择MapStruct,什么时候BeanUtils更合适。比如,如果项目已经用了SpringBeanUtils可能更方便,但如果有高性能需求,MapStruct更好。可能还要提到维护性和编译时检查的优势,比如MapStruct在编译时就能发现错误,而BeanUtils可能在运行时才报错。 另外,用户的问题可能涉及到实际应用中的例子,比如如何配置MapStruct的映射器,或者BeanUtils的基本用法。需要给出一些代码示例来对比两者的使用方式,帮助用户更直观地理解。 还要注意引用里的内容,特别是引用1详细对比了各个方面,需要整合这些信息,确保回答覆盖性能、功能、类型安全、错误处理、集成依赖和使用场景等。同时,引用4提到了日期格式化的例子,可以引用这个来说明MapStruct的灵活性。 可能需要考虑用户的实际背景,比如他们是否在Spring项目中,或者是否已经遇到了性能问题。如果用户需要处理大量数据转换,强调MapStruct的性能优势会更重要。如果只是简单的属性复制,可能BeanUtils足够。 另外,用户的问题可能隐含对学习成本的关注,MapStruct需要学习注解配置,而BeanUtils使用起来更简单。这也是一个需要提到的点,帮助用户权衡易用性和性能。 最后,总结部分需要明确两者的优缺点,并给出适用场景的建议,可能还要推荐在复杂或高性能需求下使用MapStruct,而简单场景使用BeanUtils。</think>### MapStructBeanUtils对比分析 #### 一、性能对比 1. **MapStruct** - 基于**编译时代码生成**,运行时直接调用生成的Java方法,无反射开销[^1]。 - 适合**高频调用场景**(如批量数据处理),性能接近手写代码[^2]。 - 示例性能对比: ```java // MapStruct生成代码示例(编译后) public UserVO map(UserDTO dto) { UserVO userVO = new UserVO(); userVO.setName(dto.getUsername()); userVO.setCreateTime(formatDate(dto.getCreateTime())); return userVO; } ``` 2. **BeanUtils** - 基于**反射机制**,运行时动态解析对象属性,性能较低[^1]。 - 单次调用差异小,但高频场景下性能下降显著[^3]。 | 指标 | MapStruct | BeanUtils | |-------------|-----------------|-----------------| | 10万次拷贝耗时 | ~50ms | ~500ms | | 反射开销 | 无 | 高 | --- #### 二、功能对比 1. **基础功能** - **BeanUtils** - 仅支持**同名同类型属性**的浅拷贝。 - 无法处理字段名差异、类型转换、嵌套对象。 - **MapStruct** - 支持自定义映射规则(如`@Mapping`注解)。 - 支持日期格式化、表达式计算、嵌套对象映射[^4]。 2. **高级功能** - **MapStruct特有功能** ```java @Mapper public interface UserMapper { @Mapping(target = "age", expression = "java(calculateAge(dto.getBirthday()))") @Mapping(source = "address.city", target = "city") UserVO toVO(UserDTO dto); } ``` - **类型安全校验**:编译时检查字段匹配性。 - **集合映射**:`List<UserDTO>` → `List<UserVO>`自动转换。 - **依赖注入**:支持与Spring集成。 --- #### 三、使用场景对比 | 场景 | 推荐工具 | 原因 | |---------------------|---------------|--------------------------------------------------------------------| | 高频大数据量拷贝 | MapStruct | 避免反射性能损耗,适合微服务、金融交易等高性能场景[^2] | | 简单DTO→VO属性复制 | BeanUtils | 速实现(需确保字段严格匹配),适合小型项目或原型开发 | | 字段名/类型不一致 | MapStruct | 通过`@Mapping`注解灵活配置,如日期格式化`dateFormat = "yyyy-MM-dd"`[^4] | | 需要编译时校验 | MapStruct | 提前发现字段缺失或类型错误,降低运行时风险 | | 已集成Spring框架 | BeanUtils | 无需额外依赖,直接使用`BeanUtils.copyProperties()` | --- #### 四、核心差异总结 | 维度 | MapStruct | BeanUtils | |--------------|----------------------------------------|------------------------------------| | **实现原理** | 编译时代码生成 | 运行时反射 | | **性能** | 接近手写代码(O(n)复杂度) | 反射导致O(n²)复杂度 | | **类型安全** | 编译时校验 | 运行时可能抛出`BeanException` | | **灵活性** | 支持自定义转换逻辑、嵌套映射 | 仅支持简单字段复制 | | **维护成本** | 需维护映射接口 | 代码量少但调试困难 | --- #### 五、选型建议 1. **优先选择MapStruct** - 关键路径服务(如支付核心) - 复杂领域模型(含多层嵌套对象) - 需要长期维护的项目 2. **可接受BeanUtils** - 管理后台等非性能敏感场景 - 字段完全一致的简单POJO拷贝 - 速验证原型阶段 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值