이전에 RestTemplate을 사용하면서 List 같은 Collection 형태로 반환받기 위한 방법에 대해 소개하는 글을 썼다.
2018/11/16 - [Study/Java] - RestTemplate list 반환하기
해당 방법은 ParameterizedTypeReference를 사용해서 리턴 값을 설정하는 방법인데 대략 다음과 같이 사용한다.
List<SomeDomain> someDomainList = restTemplate.exchange("url", HttpMethod.GET, null, new ParameterizedTypeReference<List<SomeDomain>> {});
ParameterizedTypeReference의 Interface는 다음과 같다.
public abstract class ParameterizedTypeReference<T> {
private final Type type;
protected ParameterizedTypeReference() {
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type");
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Assert.isTrue(actualTypeArguments.length == 1, "Number of type arguments must be 1");
this.type = actualTypeArguments[0];
}
private ParameterizedTypeReference(Type type) {
this.type = type;
}
public Type getType() {
return this.type;
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof ParameterizedTypeReference &&
this.type.equals(((ParameterizedTypeReference<?>) other).type)));
}
@Override
public int hashCode() {
return this.type.hashCode();
}
@Override
public String toString() {
return "ParameterizedTypeReference<" + this.type + ">";
}
public static <T> ParameterizedTypeReference<T> forType(Type type) {
return new ParameterizedTypeReference<T>(type) {
};
}
private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
Class<?> parent = child.getSuperclass();
if (Object.class == parent) {
throw new IllegalStateException("Expected ParameterizedTypeReference superclass");
}
else if (ParameterizedTypeReference.class == parent) {
return child;
}
else {
return findParameterizedTypeReferenceSubclass(parent);
}
}
}
ParameterizedTypeReference는 Generic <T>를 통해 전달받은 ParameterizedType으로 restTemplate의 리턴 타입을 정의하는 역할을 한다.
ParameterizedType은 java.lang.reflect 패키지에 있으며 Collection 형태의 Class Type을 의미한다.
ParameterizedTypeReference를 사용할 때는 Collection 형태로 전달해야 하며 처음 소개한 예제처럼 사용하게 된다.
그런데 이 ParameterizedTypeReference를 다음과 같이 경우에 따라 다른 값을 쓰고 싶은 경우는 어떻게 해야 할까?
// 도메인 구조
public abstract class SuperDomain {
}
public class Sub1Domain extends SuperDomain {
}
public class Sub2Domain extends SuperDomain {
}
// 호출 method
public <T extends SuperDomain> List<T> getList() {
return restTemplate.exchange("url", HttpMethod.GET, null, new ParameterizedTypeReference<List<T>> {});
}
// 호출 예
List<Sub1Domain> sub1DomainList = getList();
List<Sub2Domain> sub2DomainList = getList();
해당 예처럼 코드를 작성해도 compile 오류는 발생하지 않는다.
하지만 실행하면 runtime 시점에 class cast exception이 발생한다.
반환 값은 문제없이 캐스팅되는 선언이지만 ParameterizedTypeReference로 넘겨받은 T는 수행 시 T로 인식되어 생성자가 요구하는 ParameterizedType인 Type이 아니고 실제 Class Type을 알 수 없기 때문이다.
즉 ParameterizedTypeReference에 사용할 type은 컴파일 시점에 형 선언이 명확하게 되어있지 않으면 ParameterizedTypeReference을 통한 리턴 값 변환도 이루어지지 않는다.
Generic<T>를 외부에 위임해서 사용하려면 다음처럼 getType method를 override 하면 된다.
ResponseEntity<List<T>> response = getRestTemplate().exchange(
"url"
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<T>>() {
@Override
public Type getType() {
return parameterizedType;
}
}
);
위와 같이 사용하면 어디선가 가져온 type이 해당 호출의 리턴 값의 type이 된다.
아래는 class에 선언된 Generic Type을 가져와서 restTemplate method에서 사용하는 예제이다.
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> {
}
위 예제에서는 ParameterizedTypeReference로 넘겨받던 ParameterizedType의 generic <T>를 service의 <T>를 통해 획득하고 해당 T를 가진 Collection type인 ParameterizedType을 만들어 getType method를 override 하여 리턴 값의 타입을 명시하였다.
위와 같은 경우 각각 상속받은 Sub1Service, Sub2Service에서는 반환 값이 List<Sub1Domain>, List<Sub2Domain>이 된다.
이런 식으로 동일 restTemplate을 통해 확장된 Domain을 사용할 수 있다.
위의 예제는 Collection 반환 값의 경우인데 Domain 반환 값인 경우는 좀 더 단순하다.
@SuppressWarnings("unchecked")
public T save(T tDomain) {
Type type = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return getRestTemplate().postForObject("url", tDomain, (Class<T>) type);
}
generic type을 획득하는 부분은 Spring에서 제공하는 GenericTypeResolver를 사용하면 좀 더 유연하게 쓸 수 있다.
private Type getType() {
return Arrays.asList(GenericTypeResolver.resolveTypeArguments(this.getClass(), SuperClass.class))
.stream()
.filter(x -> TypeUtils.isAssignable(SuperDomain.class, x))
.findAny()
.get();
}
참고 : 2018/11/14 - [Study/Java] - 자주 쓰는 spring util 기록
위의 예제는 확장한 서비스에서 공통 restTemplate method를 사용한 경우인데 Class<T> 또는 Type을 매개변수로 넘겨 사용할 수도 있다.
public class SampleService {
public <T extends SuperDomain> T getList(Class<T> clazz) {
Type type = (Class<T>) clazz;
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();
}
}
List<Sub1Domain> sub1DomainList = getList(Sub1Domain.class);
List<Sub2Domain> sub2DomainList = getList(Sub2Domain.class);
'Study > Java' 카테고리의 다른 글
Spring Boot 2.3 Release Notes (0) | 2020.05.26 |
---|---|
JDK 14 New Features (0) | 2020.03.30 |
Spring Custom HandlerExceptionResolver 사용하기 (0) | 2020.02.18 |
재미로 보는 Spring Project release train naming (0) | 2020.02.07 |
OOP 개발 원칙 (0) | 2020.01.31 |
Reactor 언제 어떤 Operator를 써야 할까? (4) | 2020.01.21 |
java backend developer roadmap (0) | 2020.01.19 |
Reactor map, flatMap method는 언제 써야할까? (3) | 2020.01.16 |
Spring Boot @PropertySource 호출 순서 지정하기 (0) | 2020.01.09 |
STS 4.5.0 spring boot application 실행 불가 현상 (0) | 2020.01.03 |