Java 如何自定义ModelMapper

Java 如何自定义ModelMapper,java,rest,modelmapper,Java,Rest,Modelmapper,我想使用ModelMapper将实体转换为DTO和DTO。大多数情况下,它是有效的,但我如何定制它呢。它有如此多的选择,以至于很难决定从哪里开始。什么是最佳实践 我会在下面自己回答,但如果另一个答案更好,我会接受它。首先这里有一些链接 我对mm的印象是它设计得非常好。代码是可靠的,阅读起来很愉快。但是,文档非常简洁,示例很少。此外,api令人困惑,因为似乎有10种方法可以做任何事情,但没有说明为什么要这样或那样做 有两种选择:一种是最受欢迎的,另一种是因为易于使用而获得好评 假设你仍

我想使用ModelMapper将实体转换为DTO和DTO。大多数情况下,它是有效的,但我如何定制它呢。它有如此多的选择,以至于很难决定从哪里开始。什么是最佳实践


我会在下面自己回答,但如果另一个答案更好,我会接受它。

首先这里有一些链接

我对mm的印象是它设计得非常好。代码是可靠的,阅读起来很愉快。但是,文档非常简洁,示例很少。此外,api令人困惑,因为似乎有10种方法可以做任何事情,但没有说明为什么要这样或那样做

有两种选择:一种是最受欢迎的,另一种是因为易于使用而获得好评

假设你仍然想使用mm,下面是我对它的了解

主类,
ModelMapper
,应该是应用程序中的单例。对我来说,这意味着@Bean使用Spring。它适用于简单的情况。例如,假设您有两个类:

class DogData
{
    private String name;
    private int mass;
}

class DogInfo
{
    private String name;
    private boolean large;
}
使用适当的getter/setter。您可以这样做:

    ModelMapper mm = new ModelMapper();
    DogData dd = new DogData();
    dd.setName("fido");
    dd.setMass(70);
    DogInfo di = mm.map(dd, DogInfo.class);
“名称”将从dd复制到di

有很多方法可以自定义mm,但首先您需要了解它是如何工作的

mm对象包含每个有序类型对的TypeMap,例如和将是两个TypeMap

每个类型映射都包含一个带有映射列表的属性映射。因此,在本例中,mm将自动创建一个TypeMap,其中包含一个具有单个映射的PropertyMap

我们可以写这个

    TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
    List<Mapping> list = tm.getMappings();
    for (Mapping m : list)
    {
        System.out.println(m);
    }
当您调用mm.map()时,它就是这样做的

  • 查看类型映射是否存在,如果不存在,请为源/目标类型创建类型映射
  • 调用TypeMap条件,如果返回FALSE,则不执行任何操作并停止
  • 如有必要,调用TypeMap提供程序来构造新的目标对象
  • 调用TypeMap预转换器(如果有)
  • 执行以下操作之一:
    • 如果TypeMap有一个自定义转换器,则调用它
    • 或者,生成一个属性映射(基于配置标志加上添加的任何自定义映射),并使用它 (注意:TypeMap还具有可选的自定义Pre/PostPropertyConverter,我认为它将在每次映射之前和之后运行。)
  • 如果有类型映射,请调用类型映射后转换程序
  • 警告:这张流程图很简单,但我猜了很多,所以可能不全对

    您可以自定义此过程的每个步骤。但最常见的两种是

    • 步骤5a.–编写自定义类型映射转换器,或
    • 步骤5b.–编写自定义属性映射
    下面是一个自定义类型映射转换器的示例:

        Converter<DogData, DogInfo> myConverter = new Converter<DogData, DogInfo>()
        {
            public DogInfo convert(MappingContext<DogData, DogInfo> context)
            {
                DogData s = context.getSource();
                DogInfo d = context.getDestination();
                d.setName(s.getName());
                d.setLarge(s.getMass() > 25);
                return d;
            }
        };
    
        mm.addConverter(myConverter);
    
        Converter<Integer, Boolean> convertMassToLarge = new Converter<Integer, Boolean>()
        {
            public Boolean convert(MappingContext<Integer, Boolean> context)
            {
                // If the dog weighs more than 25, then it must be large
                return context.getSource() > 25;
            }
        };
    
        PropertyMap<DogData, DogInfo> mymap = new PropertyMap<DogData, DogInfo>()
        {
            protected void configure()
            {
                // Note: this is not normal code. It is "EDSL" so don't get confused
                map(source.getName()).setName(null);
                using(convertMassToLarge).map(source.getMass()).setLarge(false);
            }
        };
    
        mm.addMappings(mymap);
    
    在自定义属性Map.configure()中

    在本例中,我必须编写一个转换器,将整数映射为布尔值。在大多数情况下,这是不必要的,因为mm会自动将整数转换为字符串等

    我听说你也可以创建映射表达式。我试过了,但我想不出来

    最终建议和最佳实践

    默认情况下,mm使用
    匹配策略.STANDARD
    ,这是危险的。它很容易选择错误的映射,并导致奇怪的、难以发现的错误。如果明年有人在数据库中添加一个新的列呢?所以不要这样做。确保使用严格模式:

        mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    
    始终编写单元测试并确保所有映射都经过验证

        DogInfo di = mm.map(dd, DogInfo.class);
        mm.validate();   // make sure nothing in the destination is accidentally skipped
    
    如上图所示,使用
    mm.addMappings()
    修复任何验证失败


    将所有映射放在创建mm singleton的中心位置。

    我在过去6个月一直在使用它,我将解释我对此的一些想法:

    首先,建议将其作为一个独特的实例使用(singleton、SpringBean等等),这在手册中有解释,我想大家都同意这一点

    ModelMapper
    是一个强大的映射库,具有广泛的灵活性。由于它的灵活性,有许多方法可以获得相同的结果,这就是为什么它应该出现在最佳实践手册中,说明何时使用一种或另一种方法来做相同的事情

    ModelMapper
    开始有点困难,它有一个非常紧凑的学习曲线,有时不容易理解做某事的最佳方法,或者如何做其他事情。因此,开始时需要准确阅读和理解手册

    您可以使用以下设置根据需要配置映射:

    Access level
    Field matching
    Naming convention
    Name transformer
    Name tokenizer 
    Matching strategy
    
    默认配置只是最好的(),但是如果您想定制它,您可以这样做

    只有一件事与匹配策略配置有关,我认为这是最重要的配置,需要小心使用。我会使用
    严格的
    标准的
    ,但从不使用
    松散的
    ,为什么

    • 由于Loose是最灵活和智能的映射器,它可以映射一些您无法期望的属性。所以,确切地说,要小心。我认为最好创建自己的PropertyMap并在需要时使用转换器,而不是将其配置为松散的
    否则,
    验证
    所有属性匹配非常重要,您需要验证它的所有功能,而使用ModelMapper,由于智能映射是通过反射完成的,因此您将不会获得编译器帮助,它将继续编译,但映射将在未实现的情况下失败。这是我最不喜欢的事情之一,但它需要避免样板和手动映射

    最后,如果您确定要在项目中使用ModelMapper,您应该按照它建议的方式使用它,不要将它与手动映射混合使用(
        DogInfo di = mm.map(dd, DogInfo.class);
        mm.validate();   // make sure nothing in the destination is accidentally skipped
    
    Access level
    Field matching
    Naming convention
    Name transformer
    Name tokenizer 
    Matching strategy
    
    import org.modelmapper.ModelMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class EntityDtoConversionUtil {
    
        @Autowired
        private ModelMapper modelMapper;
    
        public Object convert(Object object,Class<?> type) {
    
            Object MapperObject=modelMapper.map(object, type);
    
            return MapperObject;
    
        }
    
    
    }
    
    @Entity
    class Student {
        private Long id;
        
        @OneToOne
        @JoinColumn(name = "laptop_id")
        private Laptop laptop;
    }
    
    class StudentDto {
        private Long id;
        private LaptopDto laptopDto;
    }
    
    private ModelMapper modelMapper;
    
    public StudentController(ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
        this.modelMapper.typeMap(Student.class, StudentDto.class).addMapping(Student::getLaptop, StudentDto::setLaptopDto);
    }
            
    
    modelMapper.map(student, studentDto);