Reflection - 생성자도, Setter도 없는 private 필드에 값을 넣는 방법

상우·2024년 12월 19일

상황

AddMemberAddressRequest

package com.nhnacademy.taskapi.address.domain.dto.req;

import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Getter
public class AddMemberAddressRequest{

    private String name;
    private String phoneNumber;
    private String alias;
    private String requestedTerm;
    private String zipCode;
    private String roadNameAddress;
    private String numberAddress;
    private String notes;
    private String detailAddress;
    private Integer defaultLocation;
}

AddMemberAddressRequests는
요청의 body에 담겨온 json 메시지와 바인딩 되는 객체로
기본생성자외의 생성자나 setter는 존재하지 않아
외부에서 필드에 값을 할당하는것은 불가능했다

요청의 값이 변할수도 있는 구조를 막아주고 싶어서
이러한 구조를 좋다고 생각했다.

그런데 문제는 테스트 코드였다.

  @Test
    @DisplayName("MemberAddress jpa Repository save 테스트")
    public void saveMemberAddressTest(){

        Grade grade = Grade.create("실버",1,"실버등급");
        Role role = Role.createRole("사용자","사용자입니다");
        Member member = Member.createNewMember(
                grade,
                "김갑수",
                "gapsoo01",
                "1234",
                LocalDate.parse("1992-01-01"),
                Member.Gender.M,
                "gapsoozzang@nhn.com",
                "010-1234-5678",
                role
        );

        entityManager.persist(role);
        entityManager.persist(grade);
        entityManager.persist(member);

        AddMemberAddressRequest addMemberAddressRequest = new AddMemberAddressRequest();

        MemberAddress memberAddress = MemberAddress.createMemberAddress(member,addMemberAddressRequest);

        addressRepository.save(memberAddress);

    }

테스트코드에서 AddMemberAddressRequest를 사용할 일이 있었는데
기본생성자가 있었기에 인스턴스는 만들 수 있었지만
필드값을 할당해줄 방법이 없는것이었다

테스트 코드때문에 클래스에 갑자기 setter를 추가하는것도 아닌것같고
어떻게 해야하지 고민하며 검색을 이어나가던 와중에
Reflection이란걸 알게되었다.

리플렉션에 대한 개념적인 정리는 나중에 해보려고 한다
잘 정리해 놓은 글이 있어서 여기선 이걸 참조하면 좋을것같다

참조: https://velog.io/@alsgus92/Java-Reflection%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%96%B8%EC%A0%9C%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C

해결

1. setAccessible 설정

현재 AddMemberAddressRequest의 필드들은 전부 private이다
Reflection을 사용하여 접근할 수 있지만
그러려면 setAccessible부터 해줘야한다

예시

  try {
       // private field "name" 에 대한 Access를 허용해준다
          addMemberAddressRequest.getClass().getDeclaredField("name").setAccessible(true);
       // "name" 이란 필드가 존재하지 않을경우 예외 발생
      } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
      }

나의 경우 모든 필드에 대한 Access를 다 허용해줘야 했기에
반복문을 사용했다

for(Field field : addMemberAddressRequest.getClass().getDeclaredFields()){
            field.setAccessible(true);
 }

1-1. getField VS getDeclaredField

여기서 getClass를 해준 이후에
getField , getDeclaredField 두가지 메서드가 있는걸 확인할수 있는데
이 둘은 어떤 차이일까?

  • (Field[]) getFields() : 클래스에 선언된 필드들 반환(public 접근지시자만)
  • (Field) getField(String name) : name에 해당하는 필드를 반환 (없으면 NoSuchFieldException)
  • (Field[]) getDeclaredFields() : 클래스에 선언된 모든 필드들 반환(private 까지도 포함)
  • (Field) getDeclaredField(String name) : name에 해당하는 필드를 반환(없으면 NoSuchFieldException)

참조 : https://getthismoment.tistory.com/274

2. ReflectionUtils 사용

자 이제 private 필드에도 Access가 허용되었으니
드디어 리플렉션을 사용하여 접근할 수 있게 되었다.
그렇다면 리플렉션을 사용하여 필드에 값은 어떻게 할당하는것일까?

다음과 같이 보면 이해가 빠를것 같다

  • 기존의 값 할당
    addMemberAddressRequest.name = "이순신
  • 리플렉션 사용시 값 할당
    ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("name"),
    addMemberAddressRequest, "이순신");

정리하자면

ReflectionUtils.setField( 할당 대상이 될 필드, 할당 대상이 될 객체, 할당값)

이라고 할 수 있을것 같다

결과적으로 나의경우
리플렉션을 이용하여 테스트코드를 다음과 같이 완성할 수 있었다.

    @Test
    @DisplayName("MemberAddress Repository save 테스트")
    public void saveMemberAddressTest(){

        Grade grade = Grade.create("실버",1,"실버등급");
        Role role = Role.createRole("사용자","사용자입니다");
        Member member = Member.createNewMember(
                grade,
                "김갑수",
                "gapsoo01",
                "1234",
                LocalDate.parse("1992-01-01"),
                Member.Gender.M,
                "gapsoozzang@nhn.com",
                "010-1234-5678",
                role
        );

        entityManager.persist(role);
        entityManager.persist(grade);
        entityManager.persist(member);

        AddMemberAddressRequest addMemberAddressRequest = new AddMemberAddressRequest();

        for(Field field : addMemberAddressRequest.getClass().getDeclaredFields()){
            field.setAccessible(true);
        }

        try {
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("name"),
                    addMemberAddressRequest,"이순신");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("phoneNumber"),
                    addMemberAddressRequest,"010-9999-9999");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("alias"),
                    addMemberAddressRequest,"거북선");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("requestedTerm"),
                    addMemberAddressRequest,"돌격 앞으로");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("zipCode"),
                    addMemberAddressRequest,"999-999");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("roadNameAddress"),
                    addMemberAddressRequest,"한산도");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("numberAddress"),
                    addMemberAddressRequest,"명량");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("notes"),
                    addMemberAddressRequest,"(전라남도 좌수사)");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("detailAddress"),
                    addMemberAddressRequest,"상세주소");
            ReflectionUtils.setField(addMemberAddressRequest.getClass().getDeclaredField("defaultLocation"),
                    addMemberAddressRequest,0);

        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }


        MemberAddress memberAddress = MemberAddress.createMemberAddress(member,addMemberAddressRequest);

        addressRepository.save(memberAddress);

    }
profile
엉성해도 우직하게

0개의 댓글