๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

[Spring] ๊ฐ์ฒด๋ณต์‚ฌ BeanUtils.copyProperties

by ์•ˆ์ฃผํ˜• 2022. 5. 15.

์„œ๋ก  

์Šคํ”„๋ง์„ ๊ณต๋ถ€ํ•˜๋‹ค ๋ณด๋ฉด ๊ฐ์ฒด์˜ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๋•Œ Setter ๋ฉ”์„œ๋“œ๋กœ ์ผ์ผ์ด ๋ชจ๋‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์ฝ”๋“œ์˜ ๊ธธ์ด์™€ ์ž‘์„ฑ ์‹œ๊ฐ„์ด ๋Š˜์–ด๋‚˜๊ณ , ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ BeanUtils.copyProperties๋ฅผ ํ†ตํ•ด ํ•œ ์ค„๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์›ํ•˜์ง€ ์•Š๋Š” ๊ฐ’๋“ค์€ ์ถ”๋ ค๋‚ด์–ด ์›ํ•˜๋Š” ๊ฐ’๋“ค๋งŒ ๋ณต์‚ฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

BeanUtils.copyProperties

copyProperties์€ Spring์—์„œ ์ œ๊ณตํ•˜๋Š” BeanUtils ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

BeanUtils.copyProperties(Object source, Object target, String... ignoreProperties)

  • source: ์›๋ณธ ๊ฐ์ฒด
  • target: ๋ณต์‚ฌ ๊ฐ์ฒด
  • ignoreProperties: ๋ณต์‚ฌํ•˜์ง€ ์•Š์„ ํ•„๋“œ๋ช…

ignoreProperties๋Š” ์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ƒ๋žต ์‹œ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ๋ณต์‚ฌ๋ฉ๋‹ˆ๋‹ค.

ํ•œ๋ฒˆ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์‹คํ—˜ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@SpringBootTest
class Test {


    @org.junit.jupiter.api.Test
    public void test() {
        // given
        class User {
            String name;
            int age;
            
            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

            public int getAge() {
                return age;
            }

            public void setAge(int age) {
                this.age = age;
            }


            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                User user = (User) o;
                return age == user.age && Objects.equals(name, user.name);
            }

            @Override
            public int hashCode() {
                return Objects.hash(name, age);
            }
        }

        User user1 = new User();
        user1.setName("์•ˆ์ฃผํ˜•");
        user1.setAge(25);

        // when
        User user2 = new User();
        BeanUtils.copyProperties(user1, user2);

        //then
        assertEquals(user1, user2);

        System.out.println("user1: " + user1.getName());
        System.out.println("user1: " + user1.getAge());
        System.out.println("user2: " + user2.getName());
        System.out.println("user2: " + user2.getAge());
    }
}

์œ„ ์ฝ”๋“œ์˜ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. User์˜ ์ธ์Šคํ„ด์Šค {name: "์•ˆ์ฃผํ˜•", age:25}์ธ ์ธ์Šคํ„ด์Šค user1 ์ƒ์„ฑ
  2. User์˜ ์ธ์Šคํ„ด์Šค user2๋ฅผ ์ƒ์„ฑ
  3. user1์˜ ํ•„๋“œ ๊ฐ’์„ user2์— ๋ณต์‚ฌ
  4. ๊ฐ์ฒด์˜ ์ฃผ์†Œ๊ฐ€ ์•„๋‹Œ ํ•„๋“œ ๊ฐ’์„ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด equals์™€ hashCode ์˜ค๋ฒ„๋ผ์ด๋”ฉ
  5. assertEquals(user1, user2)๋ฅผ ํ†ตํ•œ ํ…Œ์ŠคํŠธ

์ฝ”๋“œ์˜ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ์ˆ˜ํ–‰๊ฒฐ๊ณผ

copyProperties์˜ ์‹ค์ œ ๊ตฌํ˜„ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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) {
               ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
               ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);

               // Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
               boolean isAssignable =
                     (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
                           ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
                           targetResolvableType.isAssignableFrom(sourceResolvableType));

               if (isAssignable) {
                  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);
                  }
               }
            }
         }
      }
   }
}

 

์ฃผ์˜

๋ณต์‚ฌํ•˜๋ ค๋Š” ์–‘์ชฝ ๊ฐ์ฒด์— Setter๊ฐ€ ๊ตฌํ˜„๋˜์–ด ์žˆ์–ด์•ผ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

 

์ฐธ๊ณ 

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html

๋Œ“๊ธ€