rainsister
  • 신비한 비 Blog
  • Design Pattern
    • Adapter Pattern
    • Proxy Pattern
    • Mediator Pattern
    • Visitor Pattern
    • State Pattern
    • Memento Pattern
    • Factory Pattern
    • Template Pattern
    • Strategy Pattern
    • Bridge Pattern
  • springboot 2.x
    • 메시징 - 큐
      • RabbitMQ 기본설정
      • RabbitMQ 지연 큐(delayed queue)
    • Log
      • 기본 Log 설정 및 Logback 설정
      • Log4j2 사용해보기
      • tinylog 모듈 사용해보기
    • 기타 Database 에 대한 기본가이드
      • PostgreSQL
      • InfluxDB
      • MongoDB
    • 수행속도 UP! 각종 cache 어노테이션 사용법
      • Redis를 사용하여 Cache 관리하기
      • EhCache 사용해보기
      • Thread내캐쉬사용 및 Cache 어노테이션 사용법
    • Database Connection
      • Springboot 2.5 이후 data init script 초기화에 대한 변경
      • JTA 로 JPA 다중 DataSource 트랜잭션 처리 하기
      • Flyway 로 DataBase 형상 관리해보자
      • 트랜잭션 기초읽기
      • MyBatis 의 다중 DataSource
      • Spring Data JPA 다중 DataSource
      • JdbcTemplate 다중 DataSource
      • XML 로 Mybatis 설정하기
      • Mybatis 로 Mysql 연결하기
      • ORM(Spring data jpa)
      • Druid datasource 연결
      • Hikari 설정
      • JdbcTemplate 로 db 접근
    • rest api
      • XML에 대한 요청 및 응답 처리
      • SpringFox 3 및 Swagger 설정
      • 프로젝트 구동 시 RequestMappingHandler 로그 설정
      • Swagger 의 api들을 분류하는 법
      • 간단한 Restful API 만들고 테스트 코드 작성
      • Swagger2 구성하여 API 문서 자동화하기
      • JSR-303 그리고 validation
    • 설정
      • 시작
      • 멀티환경구성에 대한 새로운 방법
      • 멀티환경구성에 대한 새로운 include
      • 프로젝트 설정파일
      • 민감한 정보에 대한 암호화
  • java버전별차이
    • JAVA18
    • JAVA9
    • JAVA10
    • JAVA11
    • JAVA14
    • JAVA15
    • JAVA17
    • JAVA16
  • spring노하우
    • BeanUtils 권장하지 않는이유
    • 개인정보 암호화
    • Springboot 3가지 CROS 설정
    • Springboot 내장된 유용한 Utils
    • Spring Security WebSecurityConfigurerAdapter 가 deprecated 된 이슈해결하기
    • 아직도 HttpUtil ? SpringBoot 3.0의 HTTP Client Util 을 사용해보라
    • JDBC 소스를 뽀개기
    • spring-boot-configuration-processor 는 뭐하는놈임?
    • Apache BeanUtils vs Spring BeanUtils 성능비교
  • Effetive Java 3th
    • Finalizer & Cleaner
Powered by GitBook
On this page
  1. spring노하우

Apache BeanUtils vs Spring BeanUtils 성능비교

Previousspring-boot-configuration-processor 는 뭐하는놈임?NextEffetive Java 3th

Last updated 8 months ago

실제 프로젝트 개발 과정에서 DTO 데이터 전송 객체 및 데이터 객체 DO와 같은 소스 객체의 속성 정보를 변경하지 않고 소스 객체의 속성 정보를 기반으로 후속 작업을 수행하기 위해 서로 다른 두 객체 인스턴스를 속성으로 복사해야 하는 경우가 많으며, DO 객체의 속성을 DTO로 복사해야 하지만 객체 형식이 다르기 때문에 객체의 속성 값을 한 유형에서 다른 유형으로 변환하기 위해 매핑 코드를 작성해야 했었다.

Object 의 copy

두 Bean Utils를 구체적으로 소개하기 전에 몇 가지 기본적인 부분을 짚어넘어가 보자.두 가지 도구는 본질적으로 객체 복사 도구이고 deep coyp & shallow copy로 나뉜다.

shallow copy : 기본 데이터 유형에 대한 값 전달, 인용 데이터 유형에 대한 인용 전달과 같은 카피

deep coyp : 기본 데이터 유형에 대한 값 전달, 참조 데이터 유형에 대한 새로운 개체 생성 및 내용 복사

Apache BeanUtils 예제

publicclass PersonSource {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    // getters/setters omiited
}
publicclass PersonDest {
    private Integer id;
    private String username;
    private Integer age;
    // getters/setters omiited
}
publicclass 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}

위 예제처럼 사용방법은 매우 간단하다. 일반적인 BeanUtils 흔히 쓰는 방법은 아래와 같다.

public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
    BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

commons-beantutils는 객체의 카피에 대해 validation 과정을 거치는 데, 타입전환 을 비롯하여 객체가 속한 클래스의 접근성까지 체크하기 때문에, 상당히 복잡하다. 이런 이유로 해당 유틸은 형편없는 성능을 보장?한다. ^^

public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {
 
        // Validate existence of the specified beans
        if (dest == null) {
            thrownew IllegalArgumentException
                    ("No destination bean specified");
        }
        if (orig == null) {
            thrownew IllegalArgumentException("No origin bean specified");
        }
        if (log.isDebugEnabled()) {
            log.debug("BeanUtils.copyProperties(" + dest + ", " +
                      orig + ")");
        }
 
        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean) {
            final DynaProperty[] origDescriptors =
                ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (DynaProperty origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                // Need to check isReadable() for WrapDynaBean
                // (see Jira issue# BEANUTILS-61)
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    final Object value = ((DynaBean) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } elseif (orig instanceof Map) {
            @SuppressWarnings("unchecked")
            final
            // Map properties are always of type <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; // No point in trying to set an object's class
                }
                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) {
                        // Should not happen
                    }
                }
            }
        }
 
    }

대충 봐도 복잡하고 어렵다.

Spring 의 BeanUtils

아래와 같은 사용 예제코드를 보자

publicclass 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);
    }
}

Spring에 내장된 BeanUtils도 copyProperties 방법을 사용하여 복사하지만 구현 방식은 매우 간단하며 두 객체의 동일한 이름의 속성에 대한 간단한 get/set을 수행하여 속성의 접근성만 확인합니다.구체적으로는 다음과 같다.

private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
      @Nullable String... ignoreProperties) throws BeansException {
 
    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");
 
    Class<?> actualEditable = target.getClass();
    if (editable != null) {
      if (!editable.isInstance(target)) {
        throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
            "] not assignable to Editable class [" + editable.getName() + "]");
      }
      actualEditable = editable;
    }
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
 
    for (PropertyDescriptor targetPd : targetPds) {
      Method writeMethod = targetPd.getWriteMethod();
      if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
        PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
        if (sourcePd != null) {
          Method readMethod = sourcePd.getReadMethod();
          if (readMethod != null &&
              ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
            try {
              if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                readMethod.setAccessible(true);
              }
              Object value = readMethod.invoke(source);
              if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                writeMethod.setAccessible(true);
              }
              writeMethod.invoke(target, value);
            }
            catch (Throwable ex) {
              throw new FatalBeanException(
                  "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
            }
          }
        }
      }
    }
  }

코드에서 알수 있듯이 멤버 변수 할당은 대상 객체의 멤버 목록을 기반으로 하며, ignore를 건너뛰고 원본 객체에 존재하지 않기 때문에 이 방법은 안전하다고 볼수 있다. 두 객체 간의 구조적 차이로 인한 오류는 발생하지 않지만, 같은 이름의 두 멤버 변수 유형이 동일해야 한다.

맺으면서...

오늘 위의 두 가지 BeanUtils를 간략히 분석하였는데 Apache에 있는 BeanUtils는 성능이 좋지 않기 때문에 권장하지 않으며, Spring의 BeanUtils를 사용하거나, Dozer, ModelMapper 등과 같은 다른 copy library를 고민해볼수도 있겠다.

끝!

org..commons.beanutils.BeanUtils 은 얕은 복사(shallow copy) 다. 때문에 많은 코딩규칙 툴 혹은 plugin 에서는 해당 클래스 사용을 권장하지 않고 있다.

apache