Spring Boot Generic 적용

최민길(Gale)·2023년 6월 27일
1

Spring Boot 적용기

목록 보기
31/46

안녕하세요 오늘은 Spring Boot에서 Generic을 적용시키는 방법에 대해 포스팅해보도록 하겠습니다.

우선 제네릭이란 데이터의 타입을 일반화하는 방법입니다. Java 5부터 지원하는 방식으로 이전에는 객체를 꺼낼 때마다 형변환을 해야 했는데 제네릭을 이용해 유연하게 객체를 사용할 수 있게 되었습니다.

다음은 Aspect 골격 구현 클래스 중 일부입니다. 골격 구현 클래스이니만큼 내부에서 정의하는 메소드가 여러 객체를 반환할 수 있게 제네릭을 이용했습니다. 메소드의 경우 다음과 같이 <T>를 붙여 제네릭임을 명시합니다. 즉 명시된 T가 원하는 객체가 됩니다. 따라서 아래의 메소드는 T 객체를, 즉 자신이 원하는 객체를 리턴하는 메소드입니다.

하지만 이 과정에서 로직 내부의 결과값이 T로 반환될 수 있는지 확인해야 할 필요가 있습니다. 이를 위해선 클래스 리터럴, 즉 String.class 등 클래스 값을 참조할 필요가 있습니다. 제네릭에서는 Class<T>를 이용하여 클래스 값을 참조할 수 있습니다. 하지만 메소드의 경우 제네릭은 런타임 중에 실제 타입의 정보를 잃어버리기 때문에 단순히 Class<T>를 메소드 내에 정의하게 되면 어떤 타입인지 읽을 수 없습니다.

따라서 Class<T> 값을 매개변수로 넣어 처리합니다. 매개변수로 받게 되면 런타임 중 정의된 클래스 리터럴 값을 참조하는 것이기 떄문에 타입 체크가 가능합니다. 따라서 Class<T>를 c로 읽어온 후 c.isInstance(args.get(i))를 이용해 T로 형변환이 가능한지 체크 후 성공 시 변환한 값에 @SuppressWarning(“unchecked”)를 붙여 type-safe하다고 정의합니다. 반대의 경우는 예외를 던져 ExceptionHandler에서 처리하도록 합니다.

public <T> T getResult(final ProceedingJoinPoint joinPoint, String paramName, Class<T> c){
        final String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        final List<Object> args = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
        for (int i=0; i<parameterNames.length; i++) {
            if (parameterNames[i].equals(paramName)) {
                if(c.isInstance(args.get(i))){
                    @SuppressWarnings("unchecked") T res = (T) args.get(i);
                    return res;
                }
                else throw new ValidationException("WRONG_TYPE_EXCEPTION");
            }
        }

        throw new ValidationException("SET_BODY_TO_MDC_EXCEPTION");
    }

다음은 와일드카드에 대해 설명해보겠습니다. 와일드카드란 ? 기호로 사용하며 어떤 타입인지 신경쓰기 않고 모든 타입에 대해 열어놓겠다라는 의미입니다. 그럼 앞서 소개드린 T와의 차이가 궁금하실 수 있습니다. T의 경우 타입을 모르지만, 타입이 정해지면 그 타입의 특성에 맞게 사용합니다. 반면 와일드카드는 어떤 타입이던지 받는다라는 의미로 T 타입만을 받는 T와는 큰 차이가 존재합니다.

다음은 Response의 골격 구현 클래스입니다. 보시면 HashMap의 파라미터가 string과 ?으로 되어있습니다. Response 특성 상 여러 파라미터를 타입에 상관없이 출력해야 하므로 와일드카드를 적용했습니다. 만약 여기에 T를 정의해서 사용한다면 매번 형변환 과정을 거쳐야 하는데 response는 출력값이므로 형변환을 굳이 진행하지 않고 값 그대로 출력해도 로직에 큰 이상이 없어 와일드카드를 사용했습니다.

public abstract class Response extends AbstractModel implements BaseModel {
    final HashMap<String,?> response;

    abstract static class Builder<T extends Builder<T>>{
        HashMap<String,Object> response = new HashMap<>();

        public T add(String key, Object val){
            response.put(key,val);
            return self();
        }

        abstract Response build();

        protected abstract T self();
    }

    Response(Builder<?> builder){
        response = new HashMap<>(builder.response);
    }
}

참고 자료

https://nauni.tistory.com/m/143

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글