对象拷贝 - 优雅的解决方案 Mapstruct
MapStruct GitHub 访问地址 : https://github.com/mapstruct/mapstruct/
MapStrcut与其它工具对比以及使用说明! http://www.tuicool.com/articles/uiIRjai
是否一直在使用BeanUtils.copyProperties 用于对象属性拷贝。 出现种种小问题。
- 会将同名属性拷贝到另外一个对象中,操作方便但是存在一个缺陷 (速度慢)
在 mvc层 我们经常会DTO对象返回给前端 进行字段渲染。我们不喜欢将所有字段都显示给前端,或者我们需要修改字段返回给前端,例如 数据中存储的上架下架是0,1 但是前端需要的字段是true 和 false。 我们都得进行手动判断处理然后编辑成DTO返回给前端
MapStruct
是一种类型安全的bean
映射类生成java注释处理器
。
我们要做的就是定义一个映射器接口,声明任何必需的映射方法。在编译的过程中,MapStruct会生成此接口的实现。该实现使用纯java方法调用的源和目标对象之间的映射,MapStruct节省了时间,通过生成代码完成繁琐和容易出错的代码逻辑。。MapStruct 拥有的优点:
- 使用普通方法调用而不是反射来快速执行,他会在编译器生成相应的 Impl 方法调用时直接通过简单的 getter/setter调用而不是反射或类似的方式将值从源复制到目标
- 编译时类型安全性 : 只能映射彼此的对象和属性,不能将商品实体意外映射到用户 DTO等
- 在构建时清除错误报告,如 映射不完整 (并非所有目标属性都被映射) 或 映射不正确(无法找到适当的映射方法或类型转换)
MapStruct 提供的重要注解 :
- @Mapper : 标记这个接口作为一个映射接口,并且是编译时 MapStruct 处理器的入口
- @Mapping : 解决源对象和目标对象中,属性名字不同的情况
- Mappers.getMapper 自动生成的接口的实现可以通过 Mapper 的 class对象获取,从而让客户端可以访问 Mapper接口的实现
1 | <?xml version="1.0" encoding="UTF-8"?> |
- BasicObjectMapper包含了4个基本方法,单个和集合以及反转的单个和集合。开发中如需要对象转换操作可直接新建 interface 并继承 BasicObjectMapper,并在新建的接口上加上 @Mapper(componentModel = “spring”),如果是属性中包含其它类以及该类已经存在 Mapper 则注解中加上 users = {类名.class}。componentModel = “spring” 该配置表示生成的实现类默认加上 spring @Component 注解,使用时可直接通过 @Autowire 进行注入
1 | public interface BasicObjectMapper<SOURCE, TARGET> { |
- 直接使用进行对象数据转换
1 | @Data |
- 自定义方法添加到映射器 : 在某些情况下,需要手动实现 MapStruct 无法生成的从一种类型到另一种类型的特定映射,有如下两种实现方法 :
- 方法1> 在另一个类上实现此类方法,然后由 MapStruct 生成的映射器使用该方法
- 方法2> 在Java 8或更高版本时,可以直接在映射器界面中实现自定义方法作为默认方法。如果参数和返回类型匹配,生成的代码将调用默认方法
1 | @Mapper |
- 映射器也可以定义为抽象类的形式而不是接口,并直接在此映射器类中实现自定义方法。在这种情况下,MapStruct将生成抽象类的扩展,并实现所有抽象方法。这种方法优于声明默认方法的优点是可以在映射器类中声明附加字段
1 | @Mapper |
- 多源参数映射方法 : MapStruct 支持多个源参数的映射方法,将几个实体组合成一个数据传输对象
1 | @Mapper |
- 如果多个源对象定义了一个具有相同名称的属性,则必须使用 @Mapping 注释来指定从中检索属性的源参数,如果这种歧义未得到解决,将会引发错误。对于在给定源对象中只存在一次的属性,指定源参数的名称是可选的,因为它可以自动确定
1 | MapStruct 还提供直接引用源参数 |
- 直接字段访问映射 : MapStruct 支持 public 没有 getter/setter 的字段的映射,如果 MapStruct 无法为属性找到合适的 getter/setter方法,MapStruct 将使用这些字段作为 读/写访问器。如果它是 public,则字段被认为是读取存取器 public final。如果一个字段 static 不被视为读取存取器只有在字段被认为是写入访问者的情况下 public。如果一个字段 final 和/或 static 它不被认为是写入访问者
1 | public class Customer { |
- 检索映射器 : Mapper实例 通过 org.mapstruct.factory.Mappers 的 getMapper() 方法来检索。通常 映射器接口应该定义一个名为的成员 INSTANCE ,它包含一个映射器类型的单个实例 :
1 | @Mapper |
- 数据类型转换 : 源对象和目标对象中映射的属性类型可能不同,MapStruct 提供自动处理类型转换,提供如下自动转换 :
- 1> Java基本数据类型及其相应的包装类型,如 int 和 Integer,boolean 和 Boolean 等生成的代码是 null 转换一个包装型成相应的原始类型时一个感知,即 null 检查将被执行
- 2> Java基本号码类型和包装类型,例如之间 int 和 long 或 byte 和 Integer (大类类型数据转换成小类可能出现精度损失)
- 3> 所有Java基本类型之间 (包括其包装) 和 String 之间,例如 int 和 String 或 Boolean 和 String,java.text.DecimalFormat 均可以指定格式字符串
- int 到 String的转换
1 | int 到 String的转换 |
- 调用其他映射器 : MapStruct 中可以调用在其他类中定义的映射方法,无论是由MapStruct生成的映射器还是手写映射方法
1 | # 手动实现的映射 |
- 当为该 carToCarDto() 方法的实现生成代码时,MapStruct将查找将 Date 对象映射到String的方法,在 DateMapper 该类上找到它并生成 asString() 用于映射该 manufacturingDate 属性的调用
- 映射集合 : 集合类型(映射 List,Set 等等) 以相同的方式映射 bean类型,通过定义与在映射器接口所需的源和目标类型的映射方法。生成的代码将包含一个遍历源集合的循环,转换每个元素并将其放入目标集合中。如果在给定的映射器或其使用的映射器中找到了集合元素类型的映射方法,则会调用此方法以执行元素转换。或者,如果存在源和目标元素类型的隐式转换,则将调用此转换例程
1 | @Mapper |
- 映射枚举 : 默认情况下,源枚举中的每个常量映射到目标枚举类型中具有相同名称的常量。如果需要,可以使用 @ValueMapping 注释帮助将source enum中的常量映射为具有其他名称的常量
1 | @Mapper |
- 确定结果类型 : 当结果类型具有继承关系时,选择映射方法(@Mapping) 或工厂方法(@BeanMapping) 可能变得不明确。假设一个Apple和一个香蕉,这两个都是 Fruit的专业
1 | @Mapper(uses = FruitFactory.class) |
控制 ‘空’ 参数的映射结果 : 默认情况下 null 会返回,通过指定 nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT 上 @BeanMapping,@IterableMapping,@MapMapping,或全局上 @Mapper 或 @MappingConfig,映射结果可以被改变以返回空默认值
- 1> Bean映射 : 将返回一个 ‘空’ 目标bean,除常量和表达式外,它们将在存在时填充
- 2> 基元 : 基元的默认值将被返回,例如 false for boolean 或 0 for int
- 3> Iterables/Arrays : 一个空的迭代器将被返回
- 4> 地图 : 将返回空白地图
共享配置 : 通过指向中心接口来定义共享配置的可能性 @MapperConfig,要使映射器使用共享配置,需要在 @Mapper#config 属性中定义配置界面。该 @MapperConfig 注释具有相同的属性 @Mapper 注释。任何未通过的属性 @Mapper 都将从共享配置继承。指定 @Mapper 的属性优先于通过引用的配置类指定的属性
1 | @MapperConfig(uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR) |