目录
前言
1. 设置
1.1 Maven
2. 定义一个映射器
2.1 基本映射
2.2 指定默认值
2.2 指定默认值
2.4 dateFormat()
2.5 组合映射
2.5.1 多个源对象
2.5.2 使用其他的值
2.6 嵌套映射
2.7 numberFormat()
2.8 逆映射
2.9 继承与共享配置
2.9.1 继承配置
2.9.2 共享配置
3. 使用自定义方法
3.1 自定义类型转换方法
3.2 使用@Qualifier
3.3 使用@namd
4.集合映射
5.集成到 spring
6 高级运用
6.1 spi的运用
6.2 freemarker生成代码
Mapstruct 版本1.5.0.Beta1
官方文档
案例-github
MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。
与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:
MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。
它包含以下工件:
1.org.mapstruct:mapstruct:包含必需的注释,例如@Mapping
2.org.mapstruct:mapstruct-processor:包含注释处理器,该注释处理器生成映射器实现
对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:
org.mapstruct
mapstruct
1.5.0.Beta1
org.mapstruct
mapstruct-processor
1.5.0.Beta1
Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用的
org.projectlombok
lombok
${lombok.version}
// 版本号 1.18.12
下载插件(不是必须的,但是挺好用)
idea中下载 mapstruct support 插件,安装重启Idea:
在参数上,按 ctrl + 鼠标左键 ,能够自动进入参数所在类文件
要创建映射器,只需使用所需的映射方法定义一个Java接口,并用注释对其进行org.mapstruct.Mapper注释:
该@Mapper注释将使得MapStruct代码生成器创建的执行PersonMapper 过程中生成时的界面。
在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:
当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。
如果不指定@Mapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。
忽略字段加@Mapping#ignore() = true
@Data
public class Person {
String describe;
private String id;
private String name;
private int age;
private BigDecimal source;
private double height;
private Date createTime;
}
@Data
public class PersonDTO {
String describe;
private Long id;
private String personName;
private String age;
private String source;
private String height;
}
// mapper
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "name", source = "personName")
@Mapping(target = "id", ignore = true) // 忽略id,不进行映射
PersonDTO conver(Person person);
}
生成的实现类:
public class PersonMapperImpl implements PersonMapper {
public PersonMapperImpl() {
}
public PersonDTO conver(Person person) {
if (person == null) {
return null;
} else {
PersonDTO personDTO = new PersonDTO();
personDTO.setDescribe(person.getDescribe());
if (person.getId() != null) {
personDTO.setId(Long.parseLong(person.getId()));
}
personDTO.setPersonName(person.getName());
personDTO.setAge(String.valueOf(person.getAge()));
if (person.getSource() != null) {
personDTO.setSource(person.getSource().toString());
}
personDTO.setHeight(String.valueOf(person.getHeight()));
return personDTO;
}
}
}
测试:
@Test
public void test(){
Person person = new Person();
person.setDescribe("测试");
person.setAge(18);
person.setName("张三");
person.setHeight(170.5);
person.setSource(new BigDecimal("100"));
PersonDTO dto = PersonMapper.INSTANCT.conver(person);
System.out.println(dto);
// PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5)
}
在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue
@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
PersonDTO conver(Person person);
在@Mapper接口类里面的转换方法上添加@Mapping注解
target() 必须添加,source()可以不添加,则直接使用defaultValue
@Mapping(target = "describe", source = "describe", defaultValue = "默认值")
PersonDTO conver(Person person);
如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。当映射枚举常量时,将忽略所有其他属性类型。
....
@Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd")
PersonDTO conver(Person person);
...
impl:
try {
if (person.getCreateTime() != null) {
personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime()));
}
} catch (ParseException var4) {
throw new RuntimeException(var4);
}
@Data
public class BasicEntity {
private Date createTime;
private String createBy;
private Date updateTime;
private String updateBy;
private int _ROW;
}
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "personName",source = "name")
PersonDTO conver(Person person);
@Mapping(target = "createTime",source = "basicEntity.createTime")
PersonDTO combinationConver(Person personC, BasicEntity basicEntity);
}
...
@Mapping(target = "id", source = "id")
PersonDTO mapTo(Person person, String id);
...
虽然Person和Person有相同的id字段,但是映射器会使用mapTo方法里面的id参数。
@Data
public class Person {
...
private Child personChild;
...
}
@Data
public class PersonDTO {
...
private Child child;
...
}
// mapper
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "child", source = "personChild")
PersonDTO conver(Person person);
}
如果field name一样则不需要指定@Mapping
如果带注释的方法从数字映射到字符串,则使用DecimalFormat将格式字符串作为可处理的格式。反之亦然。对于所有其他元素类型,将被忽略。
从基本2.1 基本映射可以看出,number类型与字符串直接的转换是通过valueOf(),如果字符串格式不正确会抛出java.lang.NumberFormatException
异常,例如:Integer.valueOf(“10.2”)
使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE
异常
// mapper
....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);
...
// imppl
personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));
在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。
使用注释@InheritInverseConfiguration
表示方法应继承相应反向方法的反向配置
....
@Mapping(target = "age",source = "age", numberFormat = "#0.00")
PersonDTO conver(Person person);
@InheritInverseConfiguration
Person conver(PersonDTO dto);
...
方法级配置注解,例如@Mapping,@BeanMapping,@IterableMapping
,等等,都可以继承从一个映射方法的类似使用注释方法@InheritConfiguration
:
@Mapper
public interface CarMapper {
@Mapping(target = "numberOfSeats", source = "seatCount")
Car carDtoToCar(CarDto car);
@InheritConfiguration
void carDtoIntoCar(CarDto carDto, @MappingTarget Car car);
}
上面的示例声明了一种carDtoToCar()
具有配置的映射方法,该配置定义了应如何映射numberOfSeats
类型中的属性Car
。在现有Instance实例上执行映射的update方法Car需要相同的配置才能成功映射所有属性。通过声明@InheritConfiguration
该方法,MapStruct可以搜索继承候选,以应用继承自该方法的注释。
如果所有类型的A(源类型和结果类型)都可以分配给B的相应类型,则一个方法A可以从另一种方法B继承配置。
如果可以使用多个方法作为继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = “carDtoToCar” )。
一种方法,可以使用==@InheritConfiguration==和覆盖或通过另外施加修改的配置@Mapping,@BeanMapping
等等。
MapStruct提供了通过指向带注释的中央接口来定义共享配置的可能性@MapperConfig
。为了使映射器使用共享配置,需要在@Mapper#config
属性中定义配置接口。
该@MapperConfig
注释具有相同的属性@Mapper
注释。任何未通过via指定的属性@Mapper
都将从共享配置中继承。中指定@Mapper的属性优先于通过引用的配置类指定的属性。列表属性例如uses
可以简单组合:
@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR )
public interface CentralConfig {
}
@Mapper(config = CentralConfig.class } )
// Effective configuration:
// @Mapper(uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class },
// unmappedTargetPolicy = ReportingPolicy.ERROR // )
public interface SourceTargetMapper { ... }
共享配置config,配置一些检查策略
例如:
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:
@Mapper(uses=DateMapper.class)
public interface PersonMapper{
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
PersonDTO conver(Person person);
}
impl:
public class PersonMapperImpl implements PersonMapper {
private final DateMapper dateMapper = new DateMapper();
public PersonMapperImpl() {
}
public PersonDTO conver(Person person) {
....
personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime()));
...
return personDTO;
}
}
在进行类型转换的时候直接调用改转换方法@Mapper#uses
可以使用多个类
@Qualifier
@Qualifier
标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常
public class DateFormtUtil {
@DateFormat
public static String dateToString(Date date){
return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface DateFormat{}
}
mapper:
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
PersonDTO conver(Person person);
}
public class DateFormtUtil {
@Named("dateToString")
public static String dateToString(Date date){
return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
}
}
mapper:
@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
PersonDTO conver(Person person);
}
效果跟@Qualifier
是一样的
MapStruct
有CollectionMappingStrategy
,与可能的值:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED
和TARGET_IMMUTABLE
。
在下表中,破折号-表示属性名称。接下来,尾部s表示复数形式。该表解释了这些选项以及它们是如何施加到存在/不存在的set-s,add-s
和/或get-s
在目标对象上的方法:
选项 | 仅目标set-s可用 | 仅目标add-可用 | 既可以set-s/add- | 没有set-s/add- | 现有目标(@TargetType) |
---|---|---|---|---|---|
ACCESSOR_ONLY |
set-s | get-s | set-s | get-s | get-s |
SETTER_PREFERRED |
set-s | add- | set-s | get-s | get-s |
ADDER_PREFERRED |
set-s | add- | add- | get-s | get-s |
TARGET_IMMUTABLE |
set-s | exception | set-s | exception | set-s |
在@Mapper#componentModel
中指定依赖注入框架
@Mapper(componentModel = "spring")
public interface ModelMapper {
ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class);
ModelVO conver(Model model);
}
// 直接在类中使用Autowired注入就行了
@RestController
class MapperSpringController {
@Autowired
ModelMapper modelMapper;
@GetMapping("/get")
ModelVO getModle(){
Model model = new Model();
model.setId("123456");
model.setName("张三");
model.setCreate(new Date());
return modelMapper.conver(model);
}
}
官方文档 关于spi的运用描述
github链接