[Прохождение ямы серии] Использование BeanUtils.copyProperties, чтобы шагнуть в яму

Java

1. Необработанное преобразование

Когда дело доходит до преобразования объектов, с ним знаком каждый программист.Например, преобразование между DO,DTO и VO часто происходит в проектах.Например,пусть есть OrderDTO,и определение такое:

public class OrderDTO {
    private long id;

    private Long userId;

    private String orderNo;

    private Date gmtCreated;

    // 省略get、set方法
}

Существует OrderVO, определяемый следующим образом:

public class OrderVO {
    private long id;

    private long userId;

    private String orderNo;

    private Date gmtCreated;
  
  	// 省略get、set方法
}

Без использования каких-либо инструментов преобразования код выглядит так:

public static void main(String[] args) {
    OrderDTO orderDTO = new OrderDTO();
    orderDTO.setId(1L);
    orderDTO.setUserId(123L);
    orderDTO.setOrderNo("20210518000001");
    orderDTO.setGmtCreated(new Date());

    OrderVO orderVO = new OrderVO();
    orderVO.setId(orderDTO.getId());
    orderVO.setUserId(orderDTO.getUserId());
    orderVO.setOrderNo(orderDTO.getOrderNo());
    orderVO.setGmtCreated(orderDTO.getGmtCreated());

    System.out.println(orderVO.getId());
    System.out.println(orderVO.getUserId());
    System.out.println(orderVO.getOrderNo());
    System.out.println(orderVO.getGmtCreated());
}

результат операции:

2. Конвертируйте с помощью BeanUtils.copyProperties

Поскольку в проекте много громоздких преобразований, подобных приведенным выше, многие компании будут использовать BeanUtils.copyProperties в среде Spring для преобразования объектов.Код выглядит следующим образом:

OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);

Делается одна строчка кода, что очень удобно, а рабочий результат точно такой же, как и в оригинале.

Однако, хотя этот инструмент приносит удобство, он также приносит много проблем.Если вы не будете осторожны, вы наступите на яму.Далее мы суммируем некоторые распространенные ямы при использовании этого инструмента.

3. Шагая по пит-стопу

3.1 Тип упаковки для проблемы основного типа

java.lang.IllegalArgumentException

Если вы будете осторожны, вы можете обнаружить, что для поля userId в OrderDTO я определяю тип Long:

А поле userId в OrderVO я определяю типа long:

Затем мы запускаем код, показанный ниже:

public static void main(String[] args) {
    OrderDTO orderDTO = new OrderDTO();
    orderDTO.setId(1L);
    orderDTO.setUserId(null);
    orderDTO.setOrderNo("20210518000001");
    orderDTO.setGmtCreated(new Date());

    OrderVO orderVO = new OrderVO();
    BeanUtils.copyProperties(orderDTO, orderVO);
}

Вы увидите, что код выдаетjava.lang.IllegalArgumentExceptionаномальный:

3.2 Космическая проблема

Предположим, что поле orderNo в OrderVO определяется пользователем, и пользователь случайно вводит пробел. После использования BeanUtils.copyProperties пробел будет перенесен в поле orderNo в OrderDTO. Если вы не будете осторожны, грязные данные попадут в база данных (и мы надеемся удалить пространство, а затем удалить библиотеку), что приведет к ряду последующих проблем:

public static void main(String[] args) {
    OrderVO orderVO = new OrderVO();
    orderVO.setId(1L);
    orderVO.setUserId(123L);
    // 模拟空格场景
    orderVO.setOrderNo(" 20210518000001 ");
    orderVO.setGmtCreated(new Date());

    OrderDTO orderDTO = new OrderDTO();
    BeanUtils.copyProperties(orderVO, orderDTO);

    System.out.println(orderDTO.getOrderNo());
}

результат операции:

3.3 Не удается найти ссылку на поле

После использования BeanUtils.copyProperties вы увидите, что на поле нет ссылки, что на самом деле полезно, как показано на следующем рисунке:

Когда некоторые друзья смотрят код, когда видят, что некуда обратиться к полю, они могут не помочь, а захотеть его удалить, в результате не получается получить значение того поля, которое реально используется, в результате ошибки.

3.4 Поле дезинформации внешнего интерфейса напрямую перезаписывает базу данных

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

Если OrderVO и OrderDTO имеют следующие 2 поля:

/**
 * 已收金额
 * 单位:分
 */
private Long receivedAmount;

/**
 * 备注
 */
private String remark;

При нормальных обстоятельствах серверная часть должна использовать только поле примечания, переданное внешним интерфейсом, а поле ReceiveAmount не должно использоваться, но если пользователь изменяет примечания к заказу, внешний интерфейс случайно передает поле ReceiveAmount и значение нулевое, то после использования BeanUtils.copyProperties поле ReceiveAmount в OrderDTO также нулевое.Если бэкенд не знает, что фронтенд прошел это поле и операционная БД недостаточно строгая, полученная сумма заказа будет быть очищены, что очень страшно, и это не просто исследовать причину.

4. Рекомендации по плагинам

несмотря на то чтоBeanUtils.copyPropertiesИнструмент обеспечивает удобство, но и приносит много проблем, поэтому многие компании (в том числе моя нынешняя компания) запрещают использование инструмента в своих проектах.

Однако многократно писать преобразование объекта слишком громоздко и неэффективно, здесь мы рекомендуем плагин IDEA GenerateAllSetter, который может сгенерировать метод set объекта одним кликом, что очень удобно, как показано на следующем рисунке:

Использование плагина:

На объекте, который должен сгенерировать метод set, нажмите сочетание клавиш Option+Enter (Alt+Enter в Windows), и вы увидите параметры, показанные на следующем рисунке:

Операторы присваивания для всех полей (без значений по умолчанию) автоматически генерируются при нажатии:

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

Эффект следующий: