Почему инструмент преобразования свойств BeanUtils устарел

Java

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 также очень высока.

Самые полные вопросы интервью крупного завода

面试题1.png

面试题2.png

Обратите внимание на публичный номер: ИТ-брат, получите все виды материалов по Java (вышеприведенные вопросы для интервью — лишь малая часть)