Опыт использования ModelMapper, чтобы ступить на яму

задняя часть Google maven

Предупреждение: в этой статье много кода, пожалуйста, внимательно прочитайте его.

В практических проектах нам часто требуется преобразовать два похожих объекта друг в друга, чтобы скрыть некоторые конфиденциальные данные (такие как пароли, зашифрованные токены и т. д.) при предоставлении данных внешнему миру. Самый распространенный метод — создать новый объект и установить требуемые значения одно за другим. Если есть несколько наборов объектов, которые нужно преобразовать таким образом, то нужно просто получить/установить много бессмысленной работы.

В этом контексте родился ModelMapper, простой, эффективный и интеллектуальный инструмент картографирования объектов. Его использование очень простое, сначала добавьте зависимости maven

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>2.1.1</version>
</dependency>

Затем вы можете напрямую создать объект ModelMapper и вызвать его метод карты, чтобы сопоставить значение указанного объекта с другим объектом.

Сегодня я не буду слишком много рассказывать о методе использования, вы можете сами погуглить и найти соответствующие документы ModelMapper для изучения. Сегодня я хочу поделиться ямой, на которую я случайно наступил несколько дней назад. У меня есть два класса, PostDO и PostVO (здесь перехватываются только некоторые поля, поэтому смысл двух классов не поясняется):

public class PostDO {
    private Long id;
    private String commentId;
    private Long postId;
    private int likeNum;
}
public class PostVO {
    private Long id;
    private boolean like;
    private int likeNum;
}

В методе я пытаюсь сопоставить объект PostDO с PostVO, поэтому я делаю что-то вроде этого:

public class Application {
    public static void main(String[] args) {
        ModelMapper modelMapper = new ModelMapper();
        PostDO postDO = PostDO.builder().id(3L).likeNum(0).build();
        PostVO postVO = modelMapper.map(postDO, PostVO.class);
        System.out.println(postVO);
    }
}

Результат выполнения такой:

PostVO(id=3, like=false, likeNum=0)

Не исключение, значение поля likeNum в элементе увеличивается по мере продвижения элемента. При увеличении likeNum до 2 появилось исключение:

Exception in thread "main" org.modelmapper.MappingException: ModelMapper mapping errors:

1) Converter org.modelmapper.internal.converter.BooleanConverter@497470ed failed to convert int to boolean.

1 error
	at org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:380)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:79)
	at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:554)
	at org.modelmapper.ModelMapper.map(ModelMapper.java:387)
	at Application.main(Application.java:7)
Caused by: org.modelmapper.MappingException: ModelMapper mapping errors:

1) Error mapping 2 to boolean

1 error
	at org.modelmapper.internal.Errors.toMappingException(Errors.java:258)
	at org.modelmapper.internal.converter.BooleanConverter.convert(BooleanConverter.java:49)
	at org.modelmapper.internal.converter.BooleanConverter.convert(BooleanConverter.java:27)
	at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:298)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:108)
	at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:238)
	at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:184)
	at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:148)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:113)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:70)
	... 3 more

Очевидно, что тип подсказки int не может быть преобразован в логический тип. ModelMapper сопоставляет подобное поле с likeNum. Итак, как же отображается ModelMapper?Давайте посмотрим на исходный код ModelMapper.

ModelMapper использует механизм отражения для получения полей целевого класса и создания пар ключ-значение, которые должны совпадать, подобно этому.

键值对

Затем просмотрите эти пары ключ-значение, ища поля, которые могут быть сопоставлены в исходном классе одно за другим. Во-первых, он будет судить, существует ли соответствующее сопоставление в соответствии с целевым полем,

Mapping existingMapping = this.typeMap.mappingFor(destPath);
if (existingMapping == null) {
    this.matchSource(this.sourceTypeInfo, mutator);
    this.propertyNameInfo.clearSource();
    this.sourceTypes.clear();
}

Если нет существования, вызовите метод MatchSource, найдите поле совпадения в соответствии с соответствующим правилом в исходном классе. В процессе сопоставления вы сначала определите, существует ли тип целевого поля в списке типов. Если есть, вы можете добавить соответствующие сопоставления в соответствии с именем. Если нет существования, необходимо судить, есть ли преобразователь, который может быть нанесен на поле в Converterstore.

if (this.destinationTypes.contains(destinationMutator.getType())) {
    this.mappings.add(new PropertyMappingImpl(this.propertyNameInfo.getSourceProperties(), this.propertyNameInfo.getDestinationProperties(), true));
} else {
    TypeMap<?, ?> propertyTypeMap = this.typeMapStore.get(accessor.getType(), destinationMutator.getType(), (String)null);
    PropertyMappingImpl mapping = null;
    if (propertyTypeMap != null) {
        Converter<?, ?> propertyConverter = propertyTypeMap.getConverter();
        if (propertyConverter == null) {
            this.mergeMappings(propertyTypeMap);
        } else {
            this.mappings.add(new PropertyMappingImpl(this.propertyNameInfo.getSourceProperties(), this.propertyNameInfo.getDestinationProperties(), propertyTypeMap.getProvider(), propertyConverter));
        }

        doneMatching = this.matchingStrategy.isExact();
    } else {
        Iterator var9 = this.converterStore.getConverters().iterator();

        while(var9.hasNext()) {
            ConditionalConverter<?, ?> converter = (ConditionalConverter)var9.next();
            MatchResult matchResult = converter.match(accessor.getType(), destinationMutator.getType());
            if (!MatchResult.NONE.equals(matchResult)) {
                mapping = new PropertyMappingImpl(this.propertyNameInfo.getSourceProperties(), this.propertyNameInfo.getDestinationProperties(), false);
                if (MatchResult.FULL.equals(matchResult)) {
                    this.mappings.add(mapping);
                    doneMatching = this.matchingStrategy.isExact();
                    break;
                }

                if (!this.configuration.isFullTypeMatchingRequired()) {
                    this.partiallyMatchedMappings.add(mapping);
                    break;
                }
            }
        }
    }

    if (mapping == null) {
        this.intermediateMappings.put(accessor, new PropertyMappingImpl(this.propertyNameInfo.getSourceProperties(), this.propertyNameInfo.getDestinationProperties(), false));
    }
}

Есть 11 конвертеров по умолчанию:

转换器

После нахождения соответствующего преобразователя метод map преобразователя возвращает MatchResult, который имеет три результата: FULL, PARTIAL и NONE (т. е. все совпадения, частичные совпадения и отсутствие совпадений). Обратите внимание, что есть частичное совпадение, на которое я наступил. Поэтому, когда likeNum больше 2, его нельзя преобразовать в логический тип.

Здесь есть два решения: одно — указать в настройках, что имена полей должны точно совпадать, другое — определить стратегию сопоставления как строгую.

Способ настройки следующий:

modelMapper.getConfiguration().setFullTypeMatchingRequired(true);
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

На этом этапе ModelMapper выберет более подходящее исходное поле, но если требования соответствия невысоки, ModelMapper может отфильтровать несколько подходящих полей, поэтому потребуется дополнительная фильтрация.

PropertyMappingImpl mapping;
if (this.mappings.size() == 1) {
    mapping = (PropertyMappingImpl)this.mappings.get(0);
} else {
    mapping = this.disambiguateMappings();
    if (mapping == null && !this.configuration.isAmbiguityIgnored()) {
        this.errors.ambiguousDestination(this.mappings);
    }
}

Здесь мы видим, что если есть только один соответствующий результат, то этот результат возвращается; если есть более одного, метод isBambiguateMaintings будет вызван для удаления неоднозначных результатов. Давайте посмотрим на этот метод.

private PropertyMappingImpl disambiguateMappings() {
    List<ImplicitMappingBuilder.WeightPropertyMappingImpl> weightMappings = new ArrayList(this.mappings.size());
    Iterator var2 = this.mappings.iterator();

    while(var2.hasNext()) {
        PropertyMappingImpl mapping = (PropertyMappingImpl)var2.next();
        ImplicitMappingBuilder.SourceTokensMatcher matcher = this.createSourceTokensMatcher(mapping);
        ImplicitMappingBuilder.DestTokenIterator destTokenIterator = new ImplicitMappingBuilder.DestTokenIterator(mapping);

        while(destTokenIterator.hasNext()) {
            matcher.match(destTokenIterator.next());
        }

        double matchRatio = (double)matcher.matches() / ((double)matcher.total() + (double)destTokenIterator.total());
        weightMappings.add(new ImplicitMappingBuilder.WeightPropertyMappingImpl(mapping, matchRatio));
    }

    Collections.sort(weightMappings);
    if (((ImplicitMappingBuilder.WeightPropertyMappingImpl)weightMappings.get(0)).ratio == ((ImplicitMappingBuilder.WeightPropertyMappingImpl)weightMappings.get(1)).ratio) {
        return null;
    } else {
        return ((ImplicitMappingBuilder.WeightPropertyMappingImpl)weightMappings.get(0)).mapping;
    }
}

ModelMapper определяет вес, чтобы определить, имеет ли исходное поле неоднозначность, здесь в соответствии с правилами типа горба (или установленным для подчеркивания), разделите имя исходного и целевого поля в зависимости от количества совпадающих чисел / исходного токена + номера целевого токена, Получите коэффициент соответствия, чем больше коэффициент, тем выше соответствие. Наконец, приобретите поле, соответствующее максимальному весу. Другие поля считаются неоднозначными.

До сих пор был представлен принцип работы метода карты ModelMapper по умолчанию.В середине могут быть некоторые недостающие детали или есть что-то неясное.Добро пожаловать, чтобы обсудить со мной. При использовании ModelMapper необходимо обращать внимание на имена полей, при наличии похожих имен полей необходимо тщательно проверять правильность сопоставления и при необходимости использовать стратегию строгого сопоставления.