1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/GiteeOS-springboot-guide

Клонировать/Скачать
Apache-BeanUtils-VS-SpringBean-Utils.md 15 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 04.06.2025 10:20 f496caa

Эта статья перепечатана с https://pjmike.github.io/2018/11/03/Bean-отображение-инструменты-Apache-BeanUtils-VS-Spring-BeanUtils/, автор pjmike.

Введение

В процессе разработки наших реальных проектов мы часто сталкиваемся с необходимостью копирования атрибутов между двумя различными объектами, чтобы использовать информацию об атрибутах исходного объекта для последующих действий, не изменяя при этом исходные атрибуты объекта. Например, DTO (Data Transfer Object) и объект данных DO (Data Object). Нам нужно скопировать атрибуты объекта DO в DTO, но форматы объектов различны, поэтому нам необходимо писать код для отображения, чтобы преобразовать значения атрибутов объекта из одного типа в другой.

Копирование объектов

Перед тем как подробно рассмотреть два инструмента BeanUtils, давайте сначала обсудим некоторые базовые знания. Эти два инструмента по сути являются инструментами копирования объектов, а копирование объектов может быть глубоким или поверхностным. Давайте рассмотрим это подробнее.

Что такое поверхностное и глубокое копированиеВ Java, помимо базовых данных типов, существует также объекты класса, которые являются ссылочными данными типами. Обычно при использовании оператора "=" для присваивания, для базовых данных типов происходит копирование их значений, но для объектов происходит копирование ссылки на объект, а не самого объекта. То есть, при присваивании объекта фактически передается ссылка на исходный объект, и оба объекта указывают на один и тот же объект.Поверхностное и глубокое копирование различаются на этом основании. Если при копировании объекта копируются только базовые данные типы, а ссылочные данные типы передаются ссылкой, без создания нового объекта, это считается поверхностным копированием. В противном случае, если при копировании ссылочных данных типов создается новый объект и копируются его члены, это считается глубоким копированием.

Кратко:

  • Поверхностное копирование: копирование значений для базовых данных типов и копирование ссылок для ссылочных данных типов, это и есть поверхностное копирование.
  • Глубокое копирование: копирование значений для базовых данных типов и создание нового объекта для ссылочных данных типов с копированием его членов, это и есть глубокое копирование.

глубокое и поверхностное копирование

BeanUtils

Ранее мы кратко обсудили некоторые базовые знания о копировании объектов. Теперь давайте подробно рассмотрим два инструмента BeanUtils.### Библиотека BeanUtils Apache

Давайте рассмотрим очень простой пример использования BeanUtils.```java public class PersonSource { private Integer id; private String username; private String password; private Integer age; // getters/setters опущены } public class PersonDest { private Integer id; private String username; private Integer age; // getters/setters опущены } public class TestApacheBeanUtils { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { // Просто для отдельного тестирования PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21); PersonDest personDest = new PersonDest(); BeanUtils.copyProperties(personDest, personSource); System.out.println("persondest: " + personDest); } } persondest: PersonDest{id=1, username='pjmike', age=21}


```java
// Копирование значений из исходного объекта в целевой объект
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
    BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

По умолчанию, использование org.apache.commons.beanutils.BeanUtils для копирования сложных объектов является ссылкой, что означает поверхностное копирование.

Однако из-за низкой производительности копирования объектов в библиотеке BeanUtils Apache, ее не рекомендуется использовать. В плагине Java-разработки Alibaba также указано:

Ali-Check | Избегайте использования Apache Beanutils для копирования свойств.

Библиотека commons-beanutils добавляет много проверок при копировании объектов, включая преобразование типов и проверку доступности классов объектов, что делает ее сложной и медленной. Конкретная реализация кода приведена ниже:

public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {
```        // Проверка существования указанных объектов
        if (dest == null) {
            throw new IllegalArgumentException("Не указан объект назначения");
        }
        if (orig == null) {
            throw new IllegalArgumentException("Не указан объект источника");
        }
        if (log.isDebugEnabled()) {
            log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
        }
    }
    
    // Копирование свойств, преобразуя при необходимости
    if (orig instanceof DynaBean) {
        final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();
        for (DynaProperty origDescriptor : origDescriptors) {
            final String name = origDescriptor.getName();
            // Необходимо проверить isReadable() для WrapDynaBean
            // (см. Jira issue# BEANUTILS-61)
            if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) {
                final Object value = ((DynaBean) orig).get(name);
                copyProperty(dest, name, value);
            }
        }
    } else if (orig instanceof Map) {
        @SuppressWarnings("unchecked")
        final // Свойства Map всегда имеют тип <String, Object>
        Map<String, Object> propMap = (Map<String, Object>) orig;
        for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
            final String name = entry.getKey();
            if (getPropertyUtils().isWriteable(dest, name)) {
                copyProperty(dest, name, entry.getValue());
            }
        }
    } else /* if (orig is a standard JavaBean) */ {
        final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
        for (PropertyDescriptor origDescriptor : origDescriptors) {
            final String name = origDescriptor.getName();
            if ("class".equals(name)) {
                continue; // Нет смысла пытаться установить класс объекта
            }
            if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                     try {
                         final Object value =
                             getPropertyUtils().getSimpleProperty(orig, name);
                         copyProperty(dest, name, value);
                     } catch (final NoSuchMethodException e) {
                         // Не должно произойти
                     }
                 }
             }
         }```java
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
                                   @Nullable String... ignoreProperties) throws BeansException {

    Assert.notNull(source, "Источник не может быть null");
    Assert.notNull(target, "Цель не может быть null");
}

Spring BeanUtils

Использование BeanUtils из Spring для копирования объектов:

public class TestSpringBeanUtils {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {

        // Просто для отдельного тестирования
        PersonSource personSource = new PersonSource(1, "pjmike", "12345", 21);
        PersonDest personDest = new PersonDest();
        BeanUtils.copyProperties(personSource, personDest);
        System.out.println("persondest: " + personDest);
    }
}

BeanUtils из Spring также использует метод copyProperties для копирования, но его реализация очень проста — это просто получение и установка значений для свойств с одинаковыми именами в двух объектах, проверка доступности свойств. Конкретная реализация приведена ниже:

private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
                                   @Nullable String... ignoreProperties) throws BeansException {

    Assert.notNull(source, "Источник не может быть null");
    Assert.notNull(target, "Цель не может быть null");
}
```    Class<?> actualEditable = target.getClass();
     if (editable != null) {
         if (!editable.isInstance(target)) {
             throw new IllegalArgumentException("Класс цели [" + target.getClass().getName() +
                     "] не может быть приведен к типу редактируемого класса [" + editable.getName() + "]");
         }
         actualEditable = editable;
     }
     PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
     List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
 }
 ``````markdown
     для (PropertyDescriptor targetPd : targetPds) {
         Method writeMethod = targetPd.getWriteMethod();
         если (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
             PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
             если (sourcePd != null) {
                 Method readMethod = sourcePd.getReadMethod();
                 если (readMethod != null &&
                         ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                     попытаться {
                         если (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                             readMethod.setAccessible(true);
                         }
                         Object value = readMethod.invoke(source);
                         если (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                             writeMethod.setAccessible(true);
                         }
                         writeMethod.invoke(target, value);
                     }
                     catch (Throwable ex) {
                         бросить new FatalBeanException(
                                 "Не удалось скопировать свойство '" + targetPd.getName() + "' из источника в целевую переменную", ex);
                     }
                 }
             }
         }
     }
 ``````Увидеть можно, что присваивание членов переменных основано на списке членов целевого объекта и пропускает игнорируемые члены переменных, а также те, которые отсутствуют в исходном объекте. Поэтому этот метод безопасен и не приводит к ошибкам из-за структурных различий между двумя объектами, но **должны быть одинаковыми типы членов переменных с одинаковыми именами**```## Краткое резюме

Вышеупомянутый краткий анализ двух BeanUtils показывает, что BeanUtils из Apache имеет низкую производительность и не рекомендуется к использованию. Вместо этого можно использовать BeanUtils из Spring или другие копировальные фреймворки, такие как **[Dozer](http://dozer.sourceforge.net/documentation/gettingstarted.html)**, **[ModelMapper](http://modelmapper.org/)** и т.д. В последующих статьях я рассмотрю эти копировальные фреймворки.

## Ссылки и благодарности

- [Разговор о копировании объектов в Java-разработке](http://www.importnew.com/26306.html)
- [Подробное объяснение глубокого и поверхностного копирования в Java](https://segmentfault.com/a/1190000010648514)

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/GiteeOS-springboot-guide.git
git@api.gitlife.ru:oschina-mirror/GiteeOS-springboot-guide.git
oschina-mirror
GiteeOS-springboot-guide
GiteeOS-springboot-guide
master