利用Java反射机制和MapStruct解决具有继承关系的实体类和DTO之间的映射关系

版权声明:转载请注明出处 https://blog.csdn.net/YoshinoNanjo/article/details/82699505

       关于MapStruct的使用此处不再赘述,如有不明白的同学,可以参考MapStruct官方文档或者我人机混合翻译后的文档

       先看一下我对以下代码简要的描述,Component类是一个基类,其下有许多继承于它的子类,我这项目里有二三十个子类,这里我只列举4个:Action、ActionGroupColumn、Column、CompositeComponent,每一个子类都有自己特有的属性。ComponentDto类则包含了Component类和其所有子类的属性,因为你不可能有多少个子类就建立多少个DTO类,这么做返回给前端或者说前端传过来的数据你要做处理也是很麻烦的一件事。更详细的说明看一下代码注释就好了。

/**
 * @program: 工程名
 * @description: 组件DTO转换器
 * @author: 马赛克
 * @create: 2018/09/13 12:42:46
 **/
@Mapper
public abstract class ComponentDtoMapper extends BaseMapper {

    public static ComponentDtoMapper INSTANCE = Mappers.getMapper(ComponentDtoMapper.class);

//    可以用作缓存,暂时不需要
//    Map<Class<? extends Component>, Method> methodMap = new HashMap<>();

    /**
     * 实体类转 DTO,使用时只需要调用该方法即可,由反射机制自动调用具体的转换器
     */
    public static ComponentDto toDto(Component component) {
        try {
            Class<? extends Component> clazz = component.getClass();
            Method method = ComponentDtoMapper.class.getDeclaredMethod("toDto", clazz);
            return (ComponentDto) method.invoke(INSTANCE, component);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * DTO 转实体类,使用时只需要调用该方法即可,由反射机制自动调用具体的转换器
     */
    public static Component toEntity(ComponentDto dto) {
        try {
            // 实体类和 DTO 类中有一个属性叫 componentType 枚举类,值就是各个子类的名字
            Method method = ComponentDtoMapper.class.getDeclaredMethod("to" + dto.getComponentType(), ComponentDto.class);
            return (Component) method.invoke(INSTANCE, dto);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<ComponentDto> toDtoList(List<Component> componentList) {
        return componentList.stream()
                            .map(ComponentDtoMapper::toDto)
                            .collect(Collectors.toList());
    }

    public static List<Component> toEntityList(List<ComponentDto> dtoList) {
        return dtoList.stream()
                      .map(ComponentDtoMapper::toEntity)
                      .collect(Collectors.toList());
    }

    /**
     * Entity 转 DTO 的配置继承,该转换器是转换基类的属性,子类继承了基类,因此在转换配置上我们也可以用继承来做,下同
     *
     * @param entity 实体类
     * @return DTO
     */
    @Mappings({
            @Mapping(target = "permId", source = "perm.id"),
            @Mapping(target = "children", ignore = true)
    })
    public abstract ComponentDto toDtoField(Component entity);

    /**
     * 注意:使用了配置继承后,也可以使用 @Mappings 和 @Mapping 注解单独指定某一个属性的转换规则,下同
     */
    @InheritConfiguration(name = "toDtoField")
    public abstract ComponentDto toDto(Action entity);

    @InheritConfiguration(name = "toDtoField")
    public abstract ComponentDto toDto(ActionGroupColumn entity);

    @InheritConfiguration(name = "toDtoField")
    public abstract ComponentDto toDto(Column entity);

    @InheritConfiguration(name = "toDtoField")
    public abstract ComponentDto toDto(CompositeComponent entity);

    /**
     * DTO 转 Entity 的配置继承,因为用到了配置继承,所以不适用反向映射注解 InheritInverseConfiguration
     *
     * @param dto DTO
     * @return 实体类基类
     */
    @Mappings({
            @Mapping(target = "perm", source = "permId"),
            @Mapping(target = "children", ignore = true)
    })
    public abstract Component toEntityField(ComponentDto dto);

    // ----------------- 以下代码不适用 MapperStruct 确定结果类型的工厂模式(参考 MapperStruct 文档 9.3) --------------------

    @InheritConfiguration(name = "toEntityField")
    public abstract Action toAction(ComponentDto dto);

    @InheritConfiguration(name = "toEntityField")
    public abstract ActionGroupColumn toActionGroupColumn(ComponentDto dto);

    @InheritConfiguration(name = "toEntityField")
    public abstract Column toColumn(ComponentDto dto);

    @InheritConfiguration(name = "toEntityField")
    public abstract CompositeComponent toCompositeComponent(ComponentDto dto);

}

       我这里提到了不适用MapStruct的确定结果类型的工厂模式,是因为我使用之后发现,由DTO转换到具体的子类的时候,基类的属性有值而子类特有的属性值丢失了,相当于只转换了基类没转具体的子类,这不科学。我没有深究下去,大家可以自行试验。

       代码中涉及到部分点请参考:映射配置继承反向映射MapStruct 确定结果类型

 

展开阅读全文

没有更多推荐了,返回首页