파란하늘의 지식창고
반응형

ParameterizedTypeReference를 사용한 List response 사용

예전에 restTemplate으로 list 타입 response를 받는 것에 대해 글을 작성한 적이 있다.

2018.11.16 - [Study/Java] - RestTemplate list 반환하기

응답이 List<SomeObject> 일 때 ParameterizedTypeReference로 아래와 같이 사용한다.

ResponseEntity<List<ResultClass>> response = restTemplate.exchange("url",HttpMethod.GET, null, new ParameterizedTypeReference<List<ResultClass>>() {});
List<ResultClass> list = response.getBody();

Generic response type 사용하기

이 경우에 대해서도 글을 작성한 적이 있다.

2020.01.28 - [Study/Java] - RestTemplate Generic responseType 사용

만약 response type이 generic type parameter를 가지고 있는 경우 ParameterizedTypeReference를 다음과 같이 getType method를 override 하여 type을 결정하도록 처리하면 된다.

public abstract class SuperService<T extends superDomain> {
    public T getList() {
        Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ParameterizedType parameterizedType = new ParameterizedType() {

            @Override
            public Type[] getActualTypeArguments() {
                return new Type[]{ type };
            }

            @Override
            public Type getRawType() {
                return List.class;
            }

            @Override
            public Type getOwnerType() {
                return null;
            }
			
        };
        ResponseEntity<List<T>> response = getRestTemplate().exchange(
                "url",
                HttpMethod.GET,
                null, 
                new ParameterizedTypeReference<List<T>>() {
                    @Override
                    public Type getType() {
                        return parameterizedType;
                    }
                }
            );
        return response != null ? response.getBody() : Collections.emptyList();
    }
}

public class Sub1Service extends SuperService<Sub1Domain> {
}

public class Sub2Service extends SuperService<Sub2Domain> {
}

이전 글에서는 Collection(List) 반환에 대해서 ParameterizedType를 사용한 예제를 보여주었고 Domain 반환의 경우는 ParameterizedType을 사용하지 않고 generic type parameter를 가져와 사용하는 식으로 예제를 작성하였었다.

public abstract class SuperService<T extends superDomain> {
    public T save(T tDomain) {
        Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return getRestTemplate().postForObject("url", tDomain, (Class<T>) type);
    }
}

public class Sub1Service extends SuperService<Sub1Domain> {
}

public class Sub2Service extends SuperService<Sub2Domain> {
}

restTemplate을 호출하는 method의 class가 generic type parameter를 명시하고 있다는 전제조건 하에 해당 class를 extends 하여 확정된 generic type parameter를 가지고 있는 경우에 대한 예제였다.

컴파일 시점에 Sub1Service, Sub2Service가 Sub1Domain, Sub2Domain generic type parameter를 정의하고 있기 때문에 각각의 서비스에서 호출 시 해당 type으로 반환되게 된다.

런타임 시점에 동적으로 type을 변경하고 싶은 경우

위 경우는 service의 generic type parameter를 확장하여 정의한 경우의 사용이다.

이렇게 사용하면 동일한 method를 상위 service에 두고 하위 service는 generic type parameter만 명시하여 코드의 재사용성을 높일 수 있다.

만약 컴파일이 아닌 런타임 동작 시 동적으로 변경하고 싶은 경우 어떻게 해야 할까?
(extends 한 하위 class에서 generic type parameter를 결정하지 않고 결정되지 않은 상태로 컴파일을 하고 런타임 시점에 타입을 동적으로 정하고 싶은 경우를 말한다.)

이런 경우 ParameterizedTypeReference의 getType method를 사용하면 된다.

@Test
<T extends Object> void test5() {
    RestTemplate restTemplate = new RestTemplate();
    assertThat(restTemplate).isNotNull();

    var result = restTemplate.exchange(
        "https://petstore.swagger.io/v2/pet/1154",
        HttpMethod.GET,
        null,
        new ParameterizedTypeReference<T>() {

            @Override
            public Type getType() {
                if (1 == 1) {
                    return Result1Class.class;
                } else if (1 == 2) {
                    return Result2Class.class;
                }
                return ResultOtherClass.class;
            }

        }
    );

    log.debug("result : {}", result);
}

위의 예처럼 작성하면 컴파일 시점엔 type이 결정되지 않고 조건에 따라 type을 동적으로 변경하여 사용할 수 있다.

동적으로 변경하려는 type이 generic type parameter를 가지고 있는 경우

만약 좀 더 구조화하여 코드의 재사용성을 높이고 싶은 경우 반환되는 type이 generic type parameter를 가지는 요구조건이 있을 수 있다.

objectMapper를 사용하는 경우 이런 요구조건에 대해 처리하는 방법에 대해 이전 글에 작성하였었다.

2023.04.23 - [Study/Java] - ObjectMapper readValue generic type 사용하기

// generic type parameter를 가진 반환 대상 class
public class SomeDomain<T extend SomeExtendDomain> {

}


// type을 동적으로 반환받으려고 구현한 method
<T extends SomeExtendDomain> Class<T> getSomeExtendDomainType() {
    //상황에 따라 분기 처리를 구현
    if (조건1) {
        return SomeExtendDomain1.class
    } else if (조건2) {
        return SomeExtendDomain2.class
    } else {
        return SomeExtendDomainOther.class
    }
}


// objectMapper 호출 예제
var type = objectMapper.getTypeFactory().constructParametricType(SomeDomain.class, getSomeExtendDomainType());
var someDomain = objectMapper.readValue(new ClassPathResource("someFile.json").getFile(), type);

objectMapper의 readValue에서 generic type parameter를 가진 class로 반환받는 것과 동일하게 restTemplate에서도 반환을 받을 수 있다.

objectMapper의 경우의 예를 restTemplate 호출의 경우로 바꿔보면 다음과 같다.

// generic type parameter를 가진 반환 대상 class
public class SomeDomain<T extend SomeExtendDomain> {

}


// type을 동적으로 반환받으려고 구현한 method
<T extends SomeExtendDomain> Class<T> getSomeExtendDomainType() {
    //상황에 따라 분기 처리를 구현
    if (조건1) {
        return SomeExtendDomain1.class
    } else if (조건2) {
        return SomeExtendDomain2.class
    } else {
        return SomeExtendDomainOther.class
    }
}


// restTemplate 호출 예제
var type = ResolvableType.forClassWithGenerics(SomeDomain.class, getSomeExtendDomainType()).getType();
var someDomain = (SomeDomain) restTemplate
    .exchange(
        "http://호출주소", 
        HttpMethod.GET, 
        null, 
        ParameterizedTypeReference.forType(type), 
    ).getBody();

 

 

반응형
profile

파란하늘의 지식창고

@Bluesky_

내용이 유익했다면 광고 배너를 클릭 해주세요