文章目录

  • 1. MapStruct使用方式
  • 2. 为什么选择MapStruct
  • 3. 使用说明
  • 3.1 对接Spring框架
  • 3.2 @Mapping的使用
  • 3.2.1 target和source
  • 3.2.2 dateFormat属性
  • 3.2.3 numberFormat属性
  • 3.2.4 constant属性
  • 3.2.5 expression属性
  • 3.2.6 ignore属性
  • 3.3 @Context的使用
  • 3.3.1 集合传递自定义参数
  • 3.3.2 函数式编程实现自定义功能
  • 3.4 Map转对象


1. MapStruct使用方式

引入maven:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.3.Final</version>
</dependency>

使用mapstruct-jdk8编译可能会报java: Couldn't retrieve @Mapper annotation错误,替换成mapstruct即可。

MapStruct使用方式十分简单,创建一个interface类,并编写转换方法:

@Mapper
public interface TestMapStruct {
    TestMapStruct INSTANCE = Mappers.getMapper(TestMapStruct.class);
    ClassB aToB(ClassA a);
}

假设ClassAClassB的字段名称一模一样。使用时直接调用方法即可:

ClassB b = TestMapStruct.INSTANCE.aToB(a);

这样就能完成不同类相同字段的属性映射,十分简单。

IDEA推荐下载MapStruct Support插件,以方便看到MapStruct的注解提示。

2. 为什么选择MapStruct

TestMapStruct接口为例,MapStruct的底层原理是生成一个新类TestMapStructImpl实现TestMapStruct接口,并根据规则实现aToB方法,在aToB方法中自动根据两个类的字段名生成对应的settergetter方法,最终达到由我们自己编写settergetter方法一样的效果。因此从原理上而言,MapStruct调用时的性能和我们自己编写settergetter方法性能基本无任何差异,这是其它的BeanUtil实现方式无可比拟的。

换句话说,MapStruct的作用就是在编译时为我们自动生成对应的settergetter方法。这样的实现方式效率从本质上就已经超越了各个BeanUtil运行时根据反射动态赋值。

3. 使用说明

3.1 对接Spring框架

对接Spring框架官方提供了支持,只需要按以下配置即可:

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    ...
}

public class Test {
    @Autowired
    private TestMapStruct testMapStruct;
}

设置了componentModel="spring"后生成的实现类将会被@Component注解注释,并由Spring加载到容器中使用。

3.2 @Mapping的使用

如果需要复制属性的两个类存在部分字段名称或类型不一致时,可使用@Mapping注解来进行手动的映射。

3.2.1 target和source

赋值属性,target为目标字段,source为来源字段,target一定不能为空。名称不一致时可指定target和source名称,如:

public class ClassA {
    private String a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", source = "a");
    ClassB aToB(ClassA classA);
}

3.2.2 dateFormat属性

支持将Date属性以日期格式转成String

返回给前端的Vo类有createTime属性,为String类型,但数据库实体类型为Date,此时可以使用该属性将Date自动以某种格式转成String,如:

public class ClassA {
    private Date a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", source = "a", dateFormat = "yyyy");
    ClassB aToB(ClassA classA);
}

dateFormat使用SimpleDateFormat实现。

3.2.3 numberFormat属性

支持使用DecimalFormat方式将数字转换成String。如:

public class ClassA {
    private double a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", source = "a", numberFormat = "#.##元");
    ClassB aToB(ClassA classA);
}

3.2.4 constant属性

赋值属性,不能和其它赋值属性一起使用,配置该属性时会默认为新生成的类设置常量值。如:

public class ClassA {
    private String a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", constant = "这是B属性");
    ClassB aToB(ClassA classA);
}

3.2.5 expression属性

赋值属性,这个属性决定了MapStruct的可扩展性,不能和其它赋值一起使用,支持按expression = "java(java语句)"的方式来设置字段需要执行的java语句。如:

public class ClassA {
    private String a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", constant = "java()");
    ClassB aToB(ClassA classA);
}

3.2.6 ignore属性

配置忽略target属性,如:

public class ClassA {
    private String a;
}

public class ClassB {
    private String b;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", ignore = true);
    ClassB aToB(ClassA classA);
}

3.3 @Context的使用

@Context注解的参数将会作为方法的上下文传递到其它方法中,使用expression的java语法可对其进行操作。

3.3.1 集合传递自定义参数

MapStruct不支持集合映射时直接传递自定义参数,如下:

public class ClassA {
    private String a;
    private String test;
}

public class ClassB {
    private String b;
    private String test;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    /** 支持 */
    @Mapping(target = "b", ignore = true);
    List<ClassB> aToB(List<ClassA> classA);
    /** 不支持 */
    @Mapping(target = "b", ignore = true);
    List<ClassB> aToB(List<ClassA> classA, String test);
}

上面的的方式在编译时会报错。此时可以使用@Context注解来实现,如下:

public class ClassA {
    private String a;
    private String test;
}

public class ClassB {
    private String b;
    private String test;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", ignore = true);
    @Mapping(target = "test", expression = "java(test)");
    List<ClassB> aToB(List<ClassA> classA, @Context String test);
}

3.3.2 函数式编程实现自定义功能

假设此时有个场景,需要对某个参数进行转换,但转换逻辑较为复杂,需要调用另一个实现类方法来完成,此时我们就可以使用@Context+expression来实现这种复杂的功能。如下:

public class ClassA {
    private String a;
    private String test;
}

public class ClassB {
    private String b;
    private String test;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    @Mapping(target = "b", ignore = true);
    @Mapping(target = "test", expression = "java(test.apply(classA))");
    List<ClassB> aToB(List<ClassA> classA, @Context Function<String, String> test);
}

在调用时则只需要根据函数接口Function的操作方式传入对应的逻辑即可。@Contextexpression的组合可实现的功能非常多,这种使用方式只是抛砖引玉,Function完全也可以传入其它的实现类,在expression中编写调用方式也能实现。

3.4 Map转对象

MapStruct从1.5.3.Final版本开始支持Map转普通对象,但不支持对象转Map。如下:

public class ClassB {
    private String b;
    private String test;
}

@Mapper(componentModel = "spring")
public interface TestMapStruct {
    ClassB mapToB(Map<String, Object> map);
    List<ClassB> mapToB(List<Map<String, Object>> map);
    
    default String objectToString(Object obj) {
        return (String) obj;
    }
}

字段一致可以不指定@Mapping注解,如果不一致则需要指定target和source字段名。MapStruct会识别target和source类型,并识别是否有targetToSource方法,有的话则进行调用,也可以使用静态方法写在其它的类,使用@Mapper注解中的uses属性引入。如下:

public class TestMapper {
    /** 需要为静态方法,如果不为静态方法TestMapper则必须被Spring管理 */
    public static String objectToString(Object obj) {
        return (String) obj;
    }
}

public class ClassB {
    private String b;
    private String test;
}

@Mapper(componentModel = "spring", uses = TestMapper.class)
public interface TestMapStruct {
    ClassB mapToB(Map<String, Object> map);
    List<ClassB> mapToB(List<Map<String, Object>> map);
}

上面的这种方式也能保证识别到Object转String,并调用objectToString方法。如果TestMapper类的方法不是静态方法,则TestMapper需要和TestMapStruct注入方式保持一直。如TestMapStruct给Spring管理,则TestMapper也必须被Spring管理。