Java属性映射神器:MapStruct使用教程

一、MapStruct简介

MapStruct 是一个 Java Bean mapper,用于Java Bean 之间的转换。MapStruct 基于约定优于配置的设计思想,相较于常用的 BeanUtils.copyProperties 它更高效、优雅。

使用 MapStruct,我们只需要定义映射接口,该库在编译时会自动生成具体实现代码。

二、使用

1、引包

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.6.3</version>
        </dependency>
        <!--lombok支持-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
        </dependency>

		<!-- jdk8要额外引入 -->
		<dependency>
		    <groupId>org.mapstruct</groupId>
		    <artifactId>mapstruct-jdk8</artifactId>
		    <version>1.6.3</version>
		</dependency>

2、基本原理

1、首先,我们定义两个Java类, SimpleSource 是源类,SimpleDestination 是要映射的目标类,他们具有相同的字段。

@Data
public class SimpleSource {
    private String name;
    private String description;
    // getters and setters
}
 
@Data
public class SimpleDestination {
    private String name;
    private String description;
    // getters and setters
}

2、Mapper 接口定义
在MapStruct中,我们将类型转换器称为 mapper,请不要和Mybatis中的mapper混淆。

下面,我们定义mapper接口。使用@Mapper注解,告诉MapStruct这是我们的映射器。
注意,我们只需定义接口即可,MapStruct 会自定创建对应的实现类。

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface SimpleSourceDestinationMapper {
    SimpleSourceDestinationMapper INSTANCE = Mappers.getMapper(SimpleSourceDestinationMapper.class);

    SimpleDestination sourceToDestination(SimpleSource source);
    SimpleSource destinationToSource(SimpleDestination destination);
}


3、自动生成Mapper代码
我们重新build之后,会生成相关代码:
在这里插入图片描述

4、使用

public class Test {

    public static void main(String[] args) {
        // 创建源对象并赋值
        SimpleSource simpleSource = new SimpleSource();
        simpleSource.setName("SourceName");
        simpleSource.setDescription("SourceDescription");

        SimpleDestination simpleDestination = SimpleSourceDestinationMapper.INSTANCE.sourceToDestination(simpleSource);
        // 属性赋值了
        System.out.println(simpleDestination.getName().equals(simpleSource.getName()));
        System.out.println(simpleDestination.getDescription().equals(simpleSource.getDescription()));
    }
}

3、属性名相同时映射(包含子类型、枚举-深拷贝)

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface SimpleSourceDestinationMapper {
    SimpleSourceDestinationMapper INSTANCE = Mappers.getMapper(SimpleSourceDestinationMapper.class);

	// 直接转换即可
    SimpleDestination sourceToDestination(SimpleSource source);
    SimpleSource destinationToSource(SimpleDestination destination);
}

4、属性名不同时映射

//需要使用@Mapping注解指定转换字段的名称
//解释:source中的number转换为tagert中的myNumber
@Mapping(source = "number", target = "myNumber")
Target differentNameConvert(Source source);
  // before Java 8 java8之前
  @Mapper
  public interface MyMapper {
      @Mappings({
          @Mapping(target = "firstProperty", source = "first"),
          @Mapping(target = "secondProperty", source = "second")
      })
      HumanDto toHumanDto(Human human);
  }


  // Java 8 and later java8(包含)以后
  @Mapper
  public interface MyMapper {
      @Mapping(target = "firstProperty", source = "first"),
      @Mapping(target = "secondProperty", source = "second")
      HumanDto toHumanDto(Human human);
  }

5、默认值的使用

//需使用@Mapping中的defaultValue字段进行标识
//解释:若age字段为null,则使用defaultValue的值
@Mapping(source = "age", target = "age", defaultValue = "34")
Target defaultValueConvert(Source source);

6、日期格式映射

@Mapping(target="employeeId", source = "entity.id")
@Mapping(target="employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",
         dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);

@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",
         dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);

//需使用@mapping中的dateFormat注解进行标识转换的形式
//解释:source.date字段转换为target中的stringDate,转换格式为dateFormat指定的格式
@Mappings({
      @Mapping(source = "source.date", target = "stringDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),  //Date转换成String
      @Mapping(source = "source.stringDate", target = "date", dateFormat = "yyyy-MM-dd HH:mm")  //String转换成Date
})
Target childenConvert(Source source);

7、常量的使用

//需使用@Mapping中的constant字段进行标识
//解释:不论target中的oldNumber转换值是什么,最后都使用constant的值
@Mapping(target = "oldNumber", constant = "35")
Target constantConvert(Source source);

8、忽略某个字段转换

//需使用@Mapping中的ignore字段进行标识
//解释:忽略target中的name字段转换 不论source中name的值是什么 最后tagert中的name值都是null
@Mapping(target = "name", ignore = true)
Target ignoreConvert(Source source);

9、单独参数转换

//需标明单独参数转换后的字段名称
//解释:将传入的sex字段传递给target中的sex
@Mapping(source = "sex", target = "sex")
Target aloneParmConvert(Integer sex);

10、更新数据源

//需使用@MappingTarget字段标明
//解释:将SonTarget中的值更新给Target 要改变谁 在谁前面加@MappingTarget
void updateTargetConvert(@MappingTarget Target target, SonTarget sonTarget);

11、自定义数据类型转换

// java8
@Mapper
public interface CarMapper {

    @Mapping(...)
    ...
    CarDto carToCarDto(Car car);

    default PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}

@Mapper
public abstract class CarMapper {

    @Mapping(...)
    ...
    public abstract CarDto carToCarDto(Car car);

    public PersonDto personToPersonDto(Person person) {
        //hand-written mapping logic
    }
}
public class FishTank {
    Fish fish;
    String material;
    Quality quality;
    int length;
    int width;
    int height;
}

public class FishTankWithVolumeDto {
    FishDto fish;
    MaterialDto material;
    QualityDto quality;
    VolumeDto volume;
}

public class VolumeDto {
    int volume;
    String description;
}

@Mapper
public abstract class FishTankMapperWithVolume {

    @Mapping(target = "fish.kind", source = "source.fish.type")
    @Mapping(target = "material.materialType", source = "source.material")
    @Mapping(target = "quality.document", source = "source.quality.report")
    @Mapping(target = "volume", source = "source")
    abstract FishTankWithVolumeDto map(FishTank source);

    VolumeDto mapVolume(FishTank source) {
        int volume = source.length * source.width * source.height;
        String desc = volume < 100 ? "Small" : "Large";
        return new VolumeDto(volume, desc);
    }
}

12、多数据源转换

//需使用@Mapping标明转换时参数传递的规则
//解释:将source中的ignore字段值传递到target的ignore 将sourceTwo的id字段值传递到target中的id
@Mappings({
        @Mapping(source = "source.ignore", target = "ignore"),
        @Mapping(source = "sourceTwo.id", target = "id")
})
SonTarget multiSourceConvert(Source source, SourceTwo sourceTwo);

13、表达式的使用

//需使用@Mapping中的expression字段标识所使用的表达式
//解释:引用Java表达式中DateTuil中的date方法处理date的转换逻辑  全路径
@Mapping(target = "stringDate", expression = "java(cn.hutool.core.date.DateUtil.date(date))")
SonTarget expressionConvert(Date date);

//解释:可在接口上面的@Mapper中引入某个类,这样就可以直接写方法,不用写全路径
@Mapper(imports = {DateUtil.class})
@Mapping(target = "date", expression = "java(DateUtil.date(date))")
SonTarget expressionConvert(Date date);

14、子父类转换

//需使用@BeanMapping指定返回的类型
//解释:SonTarget是Target的子类,该方法用父类接受,返回子类属性
@BeanMapping(resultType = SonTarget.class)
Target childenConvert(Source source);

15、逆向转换

//需使用InheritInverseConfiguration标识,该注解是根据源和目标的类型去自动识别,若存在多个相同源转换目标的方法时,需指定逆向转换的方法
//解释:将differentNameConvert方法进行逆向转换
//优点:若原differentNameConvert方法上有很多@Mapping规则的话,无需再次书写即可使用
@InheritInverseConfiguration(name = "differentNameConvert")
Source convertToSource(Target target);

16、numberFormat的使用

//解释:String转数字类型后若想指定数字类型的格式可使用numberFormat指定
@Mapping(target = "number", numberFormat = "#.00")
Target differentTypeConvert(Source source);

17、@BeforeMapping的使用

//解释:在转换之前对某个实体进行操作
@BeforeMapping
default void beforeConvert(Source source){
    // some mapping logic
}

18、@AfterMapping的使用

//解释:在转换之后对某个实体进行操作
@AfterMapping
default void afterConvert(Source source){
    // some mapping logic
}

19、集合转换-无特殊要求

//解释:直接定义转换方法即可,MapStruct会自动生成Source转换成Target的方法,然后循环去调用
List<Target> convertToTargets(List<Source> sources);

20、集合转换-需自定义规则

//解释:自定义Source转换Target规则,MapStruct会自动调用sourceConvertToTarget方法来替代他自己生成的方法
//此时自定义规则才会生效
List<Target> convertToTargets(List<Source> sources);

@Mapping(source = "name", target = "name2")
Target sourceConvertToTarget(Source source);

集合转换-需自定义规则(存在多个自定义规则)

//解释:存在多个转换方法时,MapStruct无法自动选择,此时需使用@IterableMapping与@Named联合指定
//qualifiedByName指定调用的转换规则,@Named标明被调用的名称,这里的两个名称需一致
@IterableMapping(qualifiedByName = "sourceTwoConvertToTarget")
List<Target> convertToTargets(List<SourceTwo> sourceTwos);

@Mapping(source = "name", target = "name2")
@Named("sourceTwoConvertToTarget")
Target sourceTwoConvertToTarget(SourceTwo sourceTwo);

Target sourceTwoConvertToTarge2(SourceTwo sourceTwo);

21、多层嵌套映射

 @Mapper
 public interface CustomerMapper {
	// 用.表示对象层级关系
     @Mapping( target = "name", source = "record.name" )
     @Mapping( target = ".", source = "record" )
     @Mapping( target = ".", source = "account" )
     Customer customerDtoToCustomer(CustomerDto customerDto);
 }

22、spring依赖注入

某些时候尤其是在做项目时,我们用到了Sping,希望映射后的新实例是交给Spring管理。这时候就需要进行依赖注入了。只需要在Mapper接口中的@Mapper注解中加入componentModel = "spring"即可。
在这里插入图片描述

三、高级用法

1、自定义注解

可以重复使用@ToEntity

@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
@Mapping(target = "name", source = "groupName")
public @interface ToEntity { }


@Mapper
public interface StorageMapper {

    StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );

    @ToEntity
    @Mapping( target = "weightLimit", source = "maxWeight")
    ShelveEntity map(ShelveDto source);

    @ToEntity
    @Mapping( target = "label", source = "designation")
    BoxEntity map(BoxDto source);
}

2、逆映射

@Mapper
public interface CustomerMapper {

    CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );

    @Mapping(target = "name", source = "customerName")
    Customer toCustomer(CustomerDto customerDto);

    @InheritInverseConfiguration
    CustomerDto fromCustomer(Customer customer);
}

3、Map映射(同对象)

@Mapper
public interface CustomerMapper {

    @Mapping(target = "name", source = "customerName")
    Customer toCustomer(Map<String, String> map);

}

4、调用其他映射器

public class DateMapper {

    public String asString(Date date) {
        return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
            .format( date ) : null;
    }

    public Date asDate(String date) {
        try {
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                .parse( date ) : null;
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
    }
}

@Mapper(uses=DateMapper.class)
public interface CarMapper {

    CarDto carToCarDto(Car car);
}

参考资料

https://www.baeldung-cn.com/mapstruct
https://mapstruct.org/documentation/stable/reference/html/#non-shipped-annotations

03-26
### 逆向工程与反编译概述 逆向工程是一种通过对软件的目标代码进行分析,将其转化为更高级别的表示形式的过程。这一过程通常用于研究现有系统的内部结构、功能以及实现细节。在Java和Android领域,反编译工具被广泛应用于逆向工程中。 #### Java逆向工程中的Jad反编译工具 Jad是一款经典的Java反编译工具,能够将`.class`字节码文件转换为可读的`.java`源代码[^1]。虽然它可能无法完全恢复原始源代码,但它提供了足够的信息来帮助开发者理解已编译的Java程序逻辑。Jad支持多种反编译模式,并允许用户自定义规则以适应不同的需求。此外,其命令行接口和图形界面使得复杂代码的分析变得更加便捷。 #### Android逆向工程中的JEB反编译工具 针对Android应用的逆向工程,JEB是由PNF Software开发的一款专业级工具[^2]。相较于其他同类产品,JEB不仅具备强大的APK文件反编译能力,还能对Dalvik字节码执行高效而精准的操作。它的核心优势在于以下几个方面: - **广泛的平台兼容性**:除Android外,还支持ARM、MIPS等多种架构的二进制文件反汇编。 - **混淆代码解析**:内置模块能有效应对高度混淆的代码,提供分层重构机制以便于深入分析。 - **API集成支持**:允许通过编写Python或Java脚本来扩展功能并完成特定任务。 #### APK反编译流程及其意义 当涉及到具体的APK包时,可以通过一系列步骤提取其中的信息来进行全面的安全评估或者学习目的的研究工作[^3]。这些步骤一般包括但不限于获取资产目录(`assets`)内的资源数据;解密XML配置文档如`AndroidManifest.xml`定位应用程序启动点;最后利用上述提到的各种专用软件重现整个项目框架供进一步探讨。 ```bash # 使用apktool反编译APK示例 apktool d your_app.apk -o output_directory/ ``` 以上命令展示了如何借助开源工具ApkTool轻松拆卸目标安卓档案至易于探索的状态下。 ### 结论 无论是传统的桌面端还是现代移动端环境里头,恰当运用合适的反编译解决方案都是达成逆向工程项目成功不可或缺的一环。每种工具有各自专精之处,在实际应用场景当中应当依据具体需求做出明智的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值