1. Предпосылки
В предыдущей колонке я говорил о том, что "средства копирования свойств не рекомендуются". Рекомендуется напрямую определять классы и методы преобразования и использовать плагины IDEA для автоматического заполнения функций get/set.
Основными причинами нерекомендации являются:
Производительность некоторых инструментов копирования атрибутов немного низкая, а некоторые инструменты копирования атрибутов имеют «ошибки».При использовании инструментов копирования атрибутов есть некоторые скрытые опасности (будут упомянуты в последующих примерах).
2 пример
Прежде всего, компания столкнулась с реальным случаем, когда производительность копирования свойств BeanUtils пакета commons была плохой, а затем коллега перешел на BeanUtils Spring, и производительность стала намного лучше.Если вам интересно, вы можете использовать производительность фреймворк для тестирования или бенчмаркинг для сравнения.Здесь нет никакого сравнения.
Далее давайте рассмотрим проблемы с копией свойств Spring BeanUtils:
`import lombok.Data;`
`import java.util.List;`
`@Data`
`public class A {`
`private String name;`
`private List<Integer> ids;`
`}`
`@Data`
`public class B {`
`private String name;`
`private List<String> ids;`
`}`
`import org.springframework.beans.BeanUtils;`
`import java.util.Arrays;`
`public class BeanUtilDemo {`
`public static void main(String[] args) {`
`A first = new A();`
`first.setName("demo");`
`first.setIds(Arrays.asList(1, 2, 3));`
`B second = new B();`
`BeanUtils.copyProperties(first, second);`
`// 类型转换异常`
`for (String each : second.getIds()) {`
`System.out.println(each);`
`}`
`}`
`}`
При запуске приведенного выше примера возникает исключение преобразования типа.
Из точки останова видно, что после копирования свойства идентификаторы во втором объекте типа B по-прежнему имеют тип Integer:
Если он не преобразован в строку и не напечатан напрямую, об ошибке не будет сообщено.
Аналогичная проблема возникает с CGlib без определения преобразователя:
`import org.easymock.cglib.beans.BeanCopier;`
`import java.util.Arrays;`
`public class BeanUtilDemo {`
`public static void main(String[] args) {`
`A first = new A();`
`first.setName("demo");`
`first.setIds(Arrays.asList(1, 2, 3));`
`B second = new B();`
`final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false);`
`beanCopier.copy(first,second,null);`
`for (String each : second.getIds()) {// 类型转换异常`
`System.out.println(each);`
`}`
`}`
`}`
Опять же, вопрос во время выполнения выставлен.
Далее мы смотрим MapStruct:
`import org.mapstruct.Mapper;`
`import org.mapstruct.factory.Mappers;`
`@Mapper`
`public interface Converter {`
`Converter INSTANCE = Mappers.getMapper(Converter.class);`
`B aToB(A car);`
`}`
`import java.util.Arrays;`
`public class BeanUtilDemo {`
`public static void main(String[] args) {`
`A first = new A();`
`first.setName("demo");`
`first.setIds(Arrays.asList(1, 2, 3));`
`B second = Converter.INSTANCE.aToB(first);`
`for (String each : second.getIds()) {// 正常`
`System.out.println(each);`
`}`
`}`
`}`
Вы можете успешно преобразовать список в A в тип списка в B.
Давайте взглянем на класс реализации Converter, сгенерированный компиляцией:
`import java.util.ArrayList;`
`import java.util.List;`
`import javax.annotation.Generated;`
`import org.springframework.stereotype.Component;`
`@Generated(`
`value = "org.mapstruct.ap.MappingProcessor",`
`comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"`
`)`
`@Component`
`public class ConverterImpl implements Converter {`
`@Override`
`public B aToB(A car) {`
`if ( car == null ) {`
`return null;`
`}`
`B b = new B();`
`b.setName( car.getName() );`
`b.setIds( integerListToStringList( car.getIds() ) );`
`return b;`
`}`
`protected List<String> integerListToStringList(List<Integer> list) {`
`if ( list == null ) {`
`return null;`
`}`
`List<String> list1 = new ArrayList<String>( list.size() );`
`for ( Integer integer : list ) {`
`list1.add( String.valueOf( integer ) );`
`}`
`return list1;`
`}`
`}`
Автоматически выполняет преобразование для нас, мы можем не осознавать, что типы несовместимы.
Если мы добавим свойство числа String в класс A и свойство Long number в класс B, использование mapstruct сообщит об исключении .NumberFormatException, когда для числа задан нечисловой тип.
`@Override`
`public B aToB(A car) {`
`if ( car == null ) {`
`return null;`
`}`
`B b = new B();`
`b.setName( car.getName() );`
`if ( car.getNumber() != null ) { // 问题出在这里`
`b.setNumber( Long.parseLong( car.getNumber() ) );`
`}`
`b.setIds( integerListToStringList( car.getIds() ) );`
`return b;`
`}`
Использование cglib не будет отображать свойство number по умолчанию, а число в B равно null.
Если вы определяете конвертеры вручную, используйте подключаемые модули IDEA, такие как generateO2O, для их автоматического преобразования:
`public final class A2BConverter {`
`public static B from(A first) {`
`B b = new B();`
`b.setName(first.getName());`
`b.setIds(first.getIds());`
`return b;`
`}`
`}`
Эту проблему можно очень четко определить на этапе кодирования:
3 Заключение
Поскольку дженерики Java на самом деле являются проверками во время компиляции, дженерики стираются после компиляции, в результате чего во время выполнения появляются как типы List, так и List, которые можно назначать обычным образом. Это приводит к ошибкам, которые не всегда очевидны во время компиляции при использовании многих инструментов сопоставления свойств.
mapstruct настраивает процессор аннотаций и может считывать общие типы обеих сторон отображения на этапе компиляции, а затем отображать. Но такой маппинг тоже ужасен, иногда мы определяем не тот тип по невнимательности и другим причинам, и он автоматически помогает нам конвертировать, что принесет много побочных эффектов.
Производительность различных инструментов сопоставления атрибутов была кратко сравнена ранее, и результаты следующие:
Поэтому используйте инструмент преобразования атрибутов с осторожностью. Если возможно, рекомендуется настроить класс преобразования и использовать плагин IDEA для автоматического заполнения, что очень эффективно. Если какой-либо тип атрибута в A или B не совпадает, даже если атрибут удален, на этапе компиляции может быть сообщено об ошибке, и вызов может быть сделан напрямую.Эффективность get set также очень высока.
Самые полные вопросы интервью крупного завода
Обратите внимание на публичный номер: ИТ-брат, получите все виды материалов по Java (вышеприведенные вопросы для интервью — лишь малая часть)