Java 如何自定义 ModelMapper

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44534172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:34:31  来源:igfitidea点击:

How to customize ModelMapper

javarestmodelmapper

提问by John Henckel

I want to use ModelMapper to convert entity to DTO and back. Mostly it works, but how do I customize it. It has has so many options that it's hard to figure out where to start. What's best practice?

我想使用 ModelMapper 将实体转换为 DTO 并返回。大多数情况下它有效,但我如何自定义它。它有很多选择,以至于很难弄清楚从哪里开始。什么是最佳实践?

I'll answer it myself below, but if another answer is better I'll accept it.

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

采纳答案by John Henckel

First here are some links

首先这里有一些链接

My impression of mm is that it is very well engineered. The code is solid and a pleasure to read. However, the documentation is very terse, with very few examples. Also the api is confusing because there seems to be 10 ways to do anything, and no indication of why you'd do it one way or another.

我对mm的印象是它的设计非常好。代码很扎实,读起来很愉快。但是,文档非常简洁,示例很少。api 也令人困惑,因为似乎有 10 种方法可以做任何事情,并且没有迹象表明您为什么要以一种或另一种方式来做。

There are two alternatives: Dozeris the most popular, and Orikagets good reviews for ease of use.

有两种选择:Dozer是最受欢迎的,Orika 因其易用性而获得好评。

Assuming you still want to use mm, here's what I've learned about it.

假设您仍然想使用 mm,以下是我对它的了解。

The main class, ModelMapper, should be a singleton in your app. For me, that meant a @Bean using Spring. It works out of the box for simple cases. For example, suppose you have two classes:

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

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

class DogInfo
{
    private String name;
    private boolean large;
}

with appropriate getters/setters. You can do this:

使用适当的吸气剂/吸气剂。你可以这样做:

    ModelMapper mm = new ModelMapper();
    DogData dd = new DogData();
    dd.setName("fido");
    dd.setMass(70);
    DogInfo di = mm.map(dd, DogInfo.class);

and the "name" will be copied from dd to di.

并且“名称”将从dd复制到di。

There are many ways to customize mm, but first you need to understand how it works.

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

The mm object contains a TypeMap for each ordered pair of types, such as <DogInfo, DogData> and <DogData, DogInfo> would be two TypeMaps.

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

Each TypeMapcontains a PropertyMapwith a list of mappings. So in the example the mm will automatically create a TypeMap<DogData, DogInfo> that contains a PropertyMap that has a single mapping.

每个TypeMap 都包含一个带有映射列表的PropertyMap。因此,在示例中,mm 将自动创建一个 TypeMap<DogData, DogInfo>,其中包含一个具有单个映射的 PropertyMap。

We can write this

我们可以这样写

    TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
    List<Mapping> list = tm.getMappings();
    for (Mapping m : list)
    {
        System.out.println(m);
    }

and it will output

它会输出

PropertyMapping[DogData.name -> DogInfo.name]

When you call mm.map()this is what it does,

当你调用mm.map()这就是它的作用,

  1. see if the TypeMapexists yet, if not create the TypeMap for the <S, D> source/destination types
  2. call the TypeMap Condition, if it returns FALSE, do nothing and STOP
  3. call the TypeMap Providerto construct a new destination object if necessary
  4. call the TypeMap PreConverterif it has one
  5. do one of the following:
    • if the TypeMap has a custom Converter, call it
    • or, generate a PropertyMap(based on Configuration flagsplus any custom mappingsthat were added), and use it (Note: the TypeMap also has optional custom Pre/PostPropertyConverters that I thinkwill run at this point before and after each mapping.)
  6. call the TypeMap PostConverterif it has one
  1. 查看TypeMap 是否存在,如果不存在,则为 <S, D> 源/目标类型创建 TypeMap
  2. 调用 TypeMap Condition,如果它返回 FALSE,则什么都不做并停止
  3. 如有必要,调用 TypeMap Provider以构造新的目标对象
  4. 调用 TypeMap PreConverter如果它有一个
  5. 执行以下操作之一:
    • 如果 TypeMap 有自定义 Converter,则调用它
    • 或者,生成一个PropertyMap(基于配置标志以及添加的任何自定义映射),并使用它(注意:TypeMap 还具有可选的自定义 Pre/PostPropertyConverters,我认为它会在每次映射之前和之后运行。)
  6. 调用 TypeMap PostConverter如果它有一个

Caveat: This flowchart is sort of documentedbut I had to guess a lot, so it might not be all correct!

警告:这个流程图是有文件记录的,但我不得不猜测很多,所以它可能不完全正确!

You can customize every single stepof this process. But the two most common are

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

  • step 5a. – write custom TypeMap Converter, or
  • step 5b. – write custom Property Mapping.
  • 步骤 5a。– 编写自定义 TypeMap 转换器,或
  • 步骤 5b。– 编写自定义属性映射。

Here is a sample of a custom TypeMap Converter:

这是自定义 TypeMap 转换器的示例:

    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);

Notethe converter is one-way. You have to write another if you want to customize DogInfo to DogData.

请注意,转换器是单向的。如果要将 DogInfo 自定义为 DogData,则必须编写另一个。

Here is a sample of a custom PropertyMap:

这是自定义 PropertyMap的示例:

    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);

The pm.configure function is really funky. It's not actual code. It is dummy EDSL codethat gets interpreted somehow. For instance the parameter to the setter is not relevant, it is just a placeholder. You can do lots of stuff in here, such as

pm.configure 函数真的很时髦。这不是实际代码。它是以某种方式被解释的虚拟EDSL 代码。例如,setter 的参数是不相关的,它只是一个占位符。你可以在这里做很多事情,比如

  • when(condition).map(getter).setter
  • when(condition).skip().setter – safely ignore field.
  • using(converter).map(getter).setter – custom field converter
  • with(provider).map(getter).setter – custom field constructor
  • 当(条件).map(getter).setter
  • when(condition).skip().setter – 安全地忽略字段。
  • using(converter).map(getter).setter – 自定义字段转换器
  • with(provider).map(getter).setter – 自定义字段构造函数

Notethe custom mappings are addedto the default mappings, so you do notneed, for example, to specify

注意:自定义映射添加到默认的映射,这样你就不会需要,例如,指定

            map(source.getName()).setName(null);

in your custom PropertyMap.configure().

在您的自定义 PropertyMap.configure() 中。

In this example, I had to write a Converterto map Integer to Boolean. In most cases this will not be necessary because mm will automatically convert Integer to String, etc.

在这个例子中,我必须编写一个转换器来将整数映射到布尔值。在大多数情况下,这不是必需的,因为 mm 会自动将 Integer 转换为 String 等。

I'm told you can also create mappings using Java 8 lambdaexpressions. I tried, but I could not figure it out.

我听说您还可以使用 Java 8 lambda表达式创建映射。我试过了,但我无法弄清楚。

Final Recommendations and Best Practice

最终建议和最佳实践

By default mm uses MatchingStrategies.STANDARDwhich is dangerous. It can easily choose the wrong mapping and cause strange, hard to find bugs. And what if next year someone else adds a new column to the database? So don't do it. Make sure you use STRICT mode:

默认情况下 mm 使用MatchingStrategies.STANDARD这是危险的。它很容易选择错误的映射并导致奇怪的、难以发现的错误。如果明年其他人向数据库添加一个新列会怎样?所以不要这样做。确保使用 STRICT 模式:

    mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

Always write unit tests and ensure that all mappings are validated.

始终编​​写单元测试并确保所有映射都经过验证。

    DogInfo di = mm.map(dd, DogInfo.class);
    mm.validate();   // make sure nothing in the destination is accidentally skipped

Fix any validation failures with mm.addMappings()as shown above.

修复任何验证失败,mm.addMappings()如上所示。

Put all your mappings in a central place, where the mm singleton is created.

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

回答by Pau

I've been using it from last 6 months, I'm going to explain some of my thoughts about that:

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

First of all, it is recommended to use it as an unique instance (singleton, spring bean,...), that's explained in the manual, and I think all agree with that.

首先,建议将其作为唯一实例(singleton,spring bean,...)使用,手册中对此进行了解释,我认为所有人都同意这一点。

ModelMapperis a great mapping library and wide flexible. Due to its flexibility, there are many ways to get the same result, and that's why it should be in the manual of best practices of when to use one or other way to do the same thing.

ModelMapper是一个很棒的地图库并且非常灵活。由于它的灵活性,有很多方法可以获得相同的结果,这就是为什么它应该出现在最佳实践手册中,说明何时使用一种或其他方式来做同样的事情。

Starting with ModelMapperis a little bit difficult, it has a very tight learning curve and sometimes it is not easy to understand the best ways to do something, or how to do some other thing. So, to start it is required to read and understand the manual precisely.

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

You can configure your mapping as you want using the next settings:

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

Access level
Field matching
Naming convention
Name transformer
Name tokenizer 
Matching strategy

The default configuration is simply the best (http://modelmapper.org/user-manual/configuration/), but if you want to customise it you are able to do it.

默认配置是最好的(http://modelmapper.org/user-manual/configuration/),但是如果您想自定义它,您可以做到。

Just one thing related to the Matching Strategy configuration, I think this is the most important configuration and is need to be careful with it. I would use the Strictor Standardbut never the Loose, why?

仅与匹配策略配置相关的一件事,我认为这是最重要的配置,需要小心使用。我会使用StrictorStandard但从不使用Loose,为什么?

  • Due Loose is the most flexible and intelligent mapper it could be map some properties you can not expect. So, definitively, be careful with it. I think is better to create your own PropertyMap and use Converters if it is needed instead of configuring it as Loose.
  • Due Loose 是最灵活、最智能的映射器,它可以映射一些你意想不到的属性。所以,绝对要小心。我认为最好创建自己的 PropertyMap 并在需要时使用转换器,而不是将其配置为松散。

Otherwise, it is important to validateall property matches, you verify all it works, and with ModelMapper it's more need due with intelligent mapping it is done via reflection so you will not have the compiler help, it will continue compiling but the mapping will fail without realising it. That's one of the things I least like, but it needs to avoid boilerplate and manual mapping.

否则,validate所有属性匹配都很重要,您验证它的所有工作,并且使用 ModelMapper 更需要智能映射,它是通过反射完成的,因此您将无法获得编译器帮助,它将继续编译但映射将失败意识到它。这是我最不喜欢的事情之一,但它需要避免样板和手动映射。

Finally, if you are sure to use ModelMapper in your project you should use it using the way it proposes, don't mix it with manual mappings (for example), just use ModelMapper, if you don't know how to do something be sure is possible (investigate,...). Sometimes is hard to do it with model mapper (I also don't like it) as doing by hand but is the price you should pay to avoid boilerplate mappings in other POJOs.

最后,如果你确定在你的项目中使用 ModelMapper 你应该按照它建议的方式使用它,不要将它与手动映射混合(例如),只需使用 ModelMapper,如果你不知道如何做某事肯定是可能的(调查,...)。有时很难用模型映射器(我也不喜欢它)像手工那样做,但这是避免其他 POJO 中的样板映射所应该付出的代价。

回答by bhargava krishna

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;

    }


}