在实际开发中,我们经常需要在不同领域模型(如数据传输对象dto、领域实体entity、合同契约contract等)之间进行数据转换。当数据结构包含列表(list)中的嵌套对象时,且这些嵌套对象的内部属性名称在源和目标之间存在差异时,手动编写映射逻辑会变得非常繁琐和冗长。
考虑以下示例结构:
目标契约(Contract)类:
public class ResponseContractClass {
private List items;
}
public class ItemContract {
private AttributeContract attribute;
}
public class AttributeContract {
private Long idContract;
private String nameContract;
} 源实现(Impl)类:
public class ResponseImplClass {
private List items;
}
public class ItemImpl {
private AttributeImpl attribute;
}
public class AttributeImpl {
private Long idCImpl; // 注意:属性名与AttributeContract不同
private String nameImpl; // 注意:属性名与AttributeContract不同
} 可以看到,ResponseContractClass和ResponseImplClass都包含List
部的复杂结构。
MapStruct作为一个强大的Java Bean映射代码生成器,能够极大地简化这一过程。它通过生成高效、类型安全的映射实现,避免了手动编写重复的样板代码。对于上述的复杂嵌套映射问题,MapStruct提供了两种优雅的解决方案。
MapStruct的智能之处在于,当它遇到一个需要映射的复杂类型时,它会首先查找当前映射器接口中是否存在一个能够将源类型转换为目标类型的方法。如果存在,它就会自动调用该方法。我们可以利用这一特性,为AttributeImpl到AttributeContract的转换定义一个专用的映射方法。
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
@Mapper(componentModel = "spring") // 或 "default", "cdi", "jakarta.cdi" 等
public interface ResponseContractMapper {
// 主映射方法,MapStruct会递归处理其中的List- 和Item中的Attribute
ResponseContractClass mapFrom(ResponseImplClass response);
// 专门用于映射AttributeImpl到AttributeContract的方法
// MapStruct在处理ItemImpl到ItemContract时,发现需要映射AttributeImpl到AttributeContract,
// 就会自动调用此方法,并根据@Mapping注解处理属性名差异
@Mapping(target = "idContract", source = "idCImpl")
@Mapping(target = "nameContract", source = "nameImpl")
AttributeContract mapAttribute(AttributeImpl impl);
// 注意:MapStruct会自动处理List
到List的映射
// 以及ItemImpl到ItemContract的映射,只要它们内部的Attribute映射方法存在。
// 无需手动编写 ItemImpl 到 ItemContract 的映射方法,除非有额外的复杂逻辑
} 工作原理:
这种方法的优点是简洁,所有相关的映射逻辑都集中在一个映射器接口中。
当你的映射逻辑变得非常复杂,或者某些嵌套对象的映射逻辑需要在多个主映射器中复用时,将特定类型的映射逻辑抽取到独立的子映射器中是一个更好的选择。MapStruct允许通过@Mapper注解的uses属性引入其他映射器。
首先,创建一个专门负责Attribute映射的接口:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface AttributeContractMapper {
// 定义AttributeImpl到AttributeContract的映射方法
@Mapping(target = "idContract", source = "idCImpl")
@Mapping(target = "nameContract", source = "nameImpl")
AttributeContract mapFrom(AttributeImpl impl);
}然后,在主映射器中通过uses属性引用这个子映射器:
import org.mapstruct.Mapper;
import java.util.List; // 确保导入
@Mapper(componentModel = "spring", uses = AttributeContractMapper.class)
public interface ResponseContractMapper {
ResponseContractClass mapFrom(ResponseImplClass response);
// 无需在这里重复定义Attribute的映射方法,MapStruct会自动查找uses中指定的映射器
}工作原理:
优势:
通过以上两种策略,MapStruct能够优雅且高效地处理包含列表内嵌对象的复杂映射场景,即使内部属性名称不一致也能轻松应对,大大提高了开发效率和代码的可维护性。选择哪种方案取决于你的项目结构和模块化需求。对于简单场景,方案一足够;对于复杂或可复用的映射,方案二更为推荐。