Back-End

[JAVA] DTO들의 부모 DTO 인터페이스 만들기

호강하는 지해 2024. 11. 12. 17:54
728x90

내가 진행하는 프로젝트의 모든 DTO는 crtDt라는 생성일시를 담은 필드를 가지고 있다.

그리고 또한 날짜의 포맷팅을 yyyy-MM-dd HH:mm:ss로 다 통일하고 싶어서 공통으로 그리고 자동으로 처리하도록 ArgumentResolver를 만들었다. 

 

 

수정 전 ArgumentResolver

@ControllerAdvice
public class CrtDtResolver extends RequestBodyAdviceAdapter {

    // API 통신해서 받아온 LottoDrawApiResult DTO는 등록 안 해놨음

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.getParameterType().equals(UserLotto.class) ||
                methodParameter.getParameterType().equals(User.class) ||
                methodParameter.getParameterType().equals(Lotto.class) ||
                methodParameter.getParameterType().equals(DrawNum.class) ||
                methodParameter.getParameterType().equals(Stats.class) ||
                methodParameter.getParameterType().equals(Store.class);
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

        // LocalDateTime 포맷팅
        String formattedDate = now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        if (body instanceof UserLotto) {
            UserLotto userLotto = (UserLotto) body;
            userLotto.setCrtDt(formattedDate);
        } else if (body instanceof User) {
            User user = (User) body;
            user.setCrtDt(formattedDate);
        } else if (body instanceof Lotto) {
            Lotto lotto = (Lotto) body;
            lotto.setCrtDt(formattedDate);
        } else if (body instanceof DrawNum) {
            DrawNum drawNum = (DrawNum) body;
            drawNum.setCrtDt(formattedDate);
        } else if (body instanceof Stats) {
            Stats stats = (Stats) body;
            stats.setCrtDt(formattedDate);
        } else if (body instanceof Store) {
            Store store = (Store) body;
            store.setCrtDt(formattedDate);
        }
        return body;
    }
}

이게 그 당시 완성했던 코드다. 반복되는 부분이 정말 많고 길고 난잡하다.

어떻게 하면 간단하고 간략하게 줄일 수 있을까 많이 고민하다 생각해낸 방법이 있다.

 

바로 부모DTO를 인터페이스로 선언해주고 setCrtDt() 메서드를 정의해놓고 나머지 DTO들에서 부모DTO를 구현하는 것이었다.

말로하는 것보다 바로 코드로 설명하겠다.

 

 


 

 

수정 후 코드

 

부모 DTO

public interface ParentsDto {

    void setCrtDt(String crtDt);
}

이건 부모 DTO다. 인터페이스로 정의하고 setCrtDt()메서드만 작성했다.

 

 

나머지 DTO

@Getter
@Setter
@Builder
public class DrawNum implements ParentsDto {

    private Integer drawNum;                                    // 회차번호
    private String crtDt;                                       // 생성일자

    @Override
    public void setCrtDt(String crtDt) {
        this.crtDt = crtDt;
    }
}

이건 나머지 DTO들 중 하나만 가져와봤다. 

부모 DTO를 상속받고 setCrtDt()메서드를 오버라이딩 해준다.

 

☝🏻 참고로 @Setter 애노테이션이 있어도 특정 필드에 대해 수동으로 set 메서드를 작성하면, Lombok이 생성한 메서드 대신 직접 작성한 메서드가 사용된다.

 

 

수정 후 ArgumentResolver

@ControllerAdvice
public class CrtDtResolver extends RequestBodyAdviceAdapter {

    // API 통신해서 받아온 LottoDrawApiResult DTO는 제외

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return ParentsDto.class.isAssignableFrom(methodParameter.getParameterType());
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

        // LocalDateTime 포맷팅
        String formattedDate = now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        if (body instanceof ParentsDto) {
            ParentsDto dto = (ParentsDto) body;
            dto.setCrtDt(formattedDate);
        }
        return body;
    }
}

이건 최종적으로 수정한 ArgumentResolver다. 정말 간략하고 간편하게 코드가 줄었다.

 

supports()

: isAssignableFrom()를 사용하여 파라미터로 들어온 매개변수가 ParentsDto 클래스의 하위 타입인지 여부를 확인한다.

❓여기서 잠깐! 그럼 isAssignableFrom()은 instanceof랑 뭐가 다른 거지?

instanceof : 특정 Object가 어떤 클래스, 인터페이스를 상속하거나 구현했는지 확인
Class.isAssignableFrom() : 특정 Class가 어떤 클래스, 인터페이스를 상속하거나 구현했는지 확인

즉, 수행 기능은 같으나, instanceof는 객체가 존재할 때 사용할 수 있지만, isAssignableFrom()은 클래스 타입만으로 타입 계층을 확인할 수 있다.

 

afterBodyRead()

: 나는 파라미터에 @RequestBody로 객체에 값을 담은 후 내가 만든 resolver를 타게 하기 위해 afterBodyRead()를 구현한건데, 내가 지정한 포맷의 날짜를 setCrtDt() 메서드를 통해 주입해주는 역할을 한다.

 

 

 

이렇게 하면 일일히 생성일시(crtDt)를 넣지 않아도 컨트롤러를 타고 들어온 객체라면 자동으로 내가 정의한 ArgumentResolver를 통해 값이 들어가게 된다 !!!

 

 

 

 

728x90