ARCUS ์บ์๋ฅผ ์ํ Json Transcoder ํด๋์ค๋ฅผ ์ค๊ณํ๋ฉด์ ํ์ ์๊ฑฐ ๊ฐ๋ ์ ๋ํด ์ถ๊ฐ์ ์ผ๋ก ์๊ฒ ๋ ๋ด์ฉ์ด ์์ด์ ๋ธ๋ก๊ทธ์ ๋จ๊ฒจ๋๋ ค๊ณ ํ๋ค.
์ด ํด๋์ค๋ encode/decode ๋ฉ์๋๋ก ์ง๋ ฌํ/์ญ์ง๋ ฌํ๋ฅผ ์งํํ๋๋ฐ, decode์ ๊ฒฝ์ฐ์๋ ์ด๋ค ํด๋์ค๋ก ๋์ฝ๋ฉ์ ํ๋๋์ ๋ํ ๊ณ ๋ฏผ ์ง์ ์ด ์์๋ค.
์ฒ์ ์ค๊ณํ ๋, decode ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ก๋ฉํด์ Class<T>๋ TypeReference<T>๋ฅผ ๋ช ์์ ์ผ๋ก ์ ๋ฌํ๋ ๋ฉ์๋๋ฅผ ๋ง๋๋ ค๊ณ ํ๋๋ฐ, ๋ค์๊ณผ ๊ฐ์ ์๊ฐ์ด ๋ ์ฌ๋๋ค:
"์ ์ด๋ถํฐ transcoder ์ธ์คํด์ค๋ฅผ ์ ๋ค๋ฆญ <T>๋ก ๋ง๋ค๋ฉด, ๊ทธ T๋ก TypeReference<T> ์ ๋ฌ์ด ๊ฐ๋ฅํ์ง ์์๊น?"
์ฆ, new TypeReference<T>() {}๋ฅผ decode ๋ฉ์๋ ๋ด๋ถ์์ ์์ฑํ์ฌ ํ์ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ฉด ๊ตณ์ด ๋ฉ์๋๋ฅผ ์ฌ๋ฌ ๊ฐ ๋ง๋ค์ง ์์๋ ๋์ง ์์๊น ํ๋ ์๊ฐ์ด์๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด, Java์ ํ์ ์๊ฑฐ(Type Erasure) ํน์ฑ์ ๋ฐํ์์ ์ ๋ค๋ฆญ ํ์ ์ ๋ณด๊ฐ ์์ค๋์ด ์ ๋๋ก ๋์ํ์ง ์๋๋ค.
์ด ๊ณผ์ ์์ ์๊ฒ ๋ ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์.
TypeReference์ ๋์ ์๋ฆฌ
Jackson์ TypeReference๋ Super Type Token ํจํด์ ์ฌ์ฉํ์ฌ ์ ๋ค๋ฆญ ํ์ ์ ๋ณด๋ฅผ ๋ฐํ์์ ๋ณด์กดํ๋ค.
๋ด๋ถ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค:
package com.fasterxml.jackson.core.type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class TypeReference<T> implements Comparable<TypeReference<T>>
{
protected final Type _type;
protected TypeReference()
{
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof Class<?>) { // sanity check, should never happen
throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
}
/* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
* it is possible to make it fail?
* But let's deal with specific
* case when we know an actual use case, and thereby suitable
* workarounds for valid case(s) and/or error to throw
* on invalid one(s).
*/
_type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() { return _type; }
/**
* The only reason we define this method (and require implementation
* of <code>Comparable</code>) is to prevent constructing a
* reference without type information.
*/
@Override
public int compareTo(TypeReference<T> o) { return 0; }
// just need an implementation, not a good one... hence ^^^
}
์ด ํด๋์ค์ ํต์ฌ์, ์ต๋ช ๋ด๋ถ ํด๋์ค๋ฅผ ์์ฑํ ๋ ์ ๋ค๋ฆญ ํ์ ์ ๋ณด๊ฐ ์ปดํ์ผ ํ์์ ๋ณด์กด๋๋ค๋ ๊ฒ์ด๋ค.
Java์ ํ์ ์๊ฑฐ ๋ฌธ์
Java์ ์ ๋ค๋ฆญ์ ์ปดํ์ผ ํ์์๋ง ์กด์ฌํ๊ณ ๋ฐํ์์๋ ์๊ฑฐ๋๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์, ๋ฉ์๋ ๋ด๋ถ์์ new TypeReference<T>() {}๋ฅผ ์์ฑํ๋ฉด, T๋ ์ด๋ฏธ Object๋ก ํ์ ์๊ฑฐ๋ ์ํ์ด๋ฏ๋ก ์๋ฏธ๊ฐ ์์ด์ง๋ค.
์ ํํ ์์๋ฅผ ๋ค์ด๋ณด์๋ฉด
TestTranscoder<List<Employee>> transcoder = new TestTranscoder<>();
์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ์ ๋, ์ปดํ์ผ๋ฌ๋ T๊ฐ List<Employee> ๋ผ๋ ๊ฒ์ ์๊ณ ์๋ค.
ํ์ง๋ง ์ด ํ์ ์ ๋ณด๋ ๋ฐํ์์ ๋ชจ๋ ์ง์์ง๋ฏ๋ก, ์๋ฐ๋ TestTranscoder๊ฐ ์ด๋ค T๋ก ์ธ์คํด์คํ๋์๋์ง ๋ฐํ์์๋ ์ ์๊ฐ ์๋ค.
์ฌ๋ฐ๋ฅธ ์ฌ์ฉ๋ฒ
TypeReference<Employee> typeRef = new TypeReference<Employee>() {}; // ์ต๋ช
๋ด๋ถ ํด๋์ค ์์ฑ
Employee employee = objectMapper.readValue(json, typeRef);
์๋ชป๋ ์ฌ์ฉ๋ฒ
public <T> Object decode(byte[] data) {
TypeReference<T> typeRef = new TypeReference<T>() {}; // T๋ ์ด๋ฏธ ์๊ฑฐ๋จ
return objectMapper.readValue(data, typeRef);
}
์ค์ ํ ์คํธ ์ฝ๋๋ก ๊ฒ์ฆ
์ ๋ด์ฉ์ ํ ์คํธ ์ฝ๋๋ฅผ ํตํด ๊ฒ์ฆํด๋ณด๊ณ ์ Employee, TestTranscoder ํด๋์ค๋ฅผ ์์๋ก ๊ตฌํํ์๋ค.
public class TestTranscoder<T> {
private final ObjectMapper objectMapper;
public TestTranscoder(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// ๋ฐฉ๋ฒ 1: Class ๋ช
์์ ์ ๋ฌ (์ ์)
public T decodeWithClass(byte[] data, Class<T> clazz) {
try {
return objectMapper.readValue(data, clazz);
} catch (Exception e) {
throw new RuntimeException("Decode with class failed", e);
}
}
// ๋ฐฉ๋ฒ 2: TypeReference ๋ช
์์ ์ ๋ฌ (์ ์)
public T decodeWithTypeReference(byte[] data, TypeReference<T> typeRef) {
try {
return objectMapper.readValue(data, typeRef);
} catch (Exception e) {
throw new RuntimeException("Decode with TypeReference failed", e);
}
}
// ๋ฐฉ๋ฒ 3: ์ ๋ค๋ฆญ T๋ก TypeReference ์์ฑ (ํ์
์๊ฑฐ)
public Object decodeWithGenericTypeReference(byte[] data) {
try {
// ์ด ๋ฐฉ์์ ํ์
์ ๋ณด๊ฐ ์ด๋ฏธ ์์ค๋ ์ํ
TypeReference<T> typeRef = new TypeReference<T>() {};
return objectMapper.readValue(data, typeRef);
} catch (Exception e) {
throw new RuntimeException("Decode with generic TypeReference failed", e);
}
}
public byte[] encode(T object) {
try {
return objectMapper.writeValueAsBytes(object);
} catch (Exception e) {
throw new RuntimeException("Encode failed", e);
}
}
}
ํ ์คํธ ์ผ์ด์ค
ํ ์คํธ๋ ์ ๋ถ ํต๊ณผํ๋ค.
class TestTranscoderTest {
private ObjectMapper objectMapper;
private TestTranscoder<Employee> employeeTranscoder;
private TestTranscoder<List<Employee>> listTranscoder;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
employeeTranscoder = new TestTranscoder<>(objectMapper);
listTranscoder = new TestTranscoder<>(objectMapper);
}
@Test
@DisplayName("Class๋ฅผ ์ฌ์ฉํ ์ญ์ง๋ ฌํ๋ ์ ์ ๋์ํ๋ค")
void testDecodeWithClass_Success() {
// Given
Employee original = new Employee(1L, "๊น์ง์", null);
byte[] serialized = employeeTranscoder.encode(original);
// When
Employee decoded = employeeTranscoder.decodeWithClass(serialized, Employee.class);
// Then
assertThat(decoded).isNotNull();
assertThat(decoded).isInstanceOf(Employee.class);
assertThat(decoded.getId()).isEqualTo(1L);
assertThat(decoded.getName()).isEqualTo("๊น์ง์");
}
@Test
@DisplayName("๋ช
์์ TypeReference๋ฅผ ์ฌ์ฉํ ์ญ์ง๋ ฌํ๋ ์ ์ ๋์ํ๋ค")
void testDecodeWithExplicitTypeReference_Success() {
// Given
Employee original = new Employee(1L, "๊น์ง์", null);
byte[] serialized = employeeTranscoder.encode(original);
TypeReference<Employee> typeRef = new TypeReference<Employee>() {};
// When
Employee decoded = employeeTranscoder.decodeWithTypeReference(serialized, typeRef);
// Then
assertThat(decoded).isNotNull();
assertThat(decoded).isInstanceOf(Employee.class);
assertThat(decoded.getId()).isEqualTo(1L);
assertThat(decoded.getName()).isEqualTo("๊น์ง์");
}
@Test
@DisplayName("์ ๋ค๋ฆญ T๋ก ์์ฑํ TypeReference๋ ํ์
์ ๋ณด๊ฐ ์์ค๋๋ค")
void testDecodeWithGenericTypeReference_TypeErasure() {
// Given
Employee original = new Employee(1L, "๊น์ง์", null);
byte[] serialized = employeeTranscoder.encode(original);
// When
Object decoded = employeeTranscoder.decodeWithGenericTypeReference(serialized);
// Then
assertThat(decoded).isNotNull();
// ํ์
์๊ฑฐ๋ก ์ธํด Employee๊ฐ ์๋ Map์ผ๋ก ์ญ์ง๋ ฌํ๋จ
assertThat(decoded).isInstanceOf(Map.class);
assertThat(decoded).isNotInstanceOf(Employee.class);
// Map์ผ๋ก ์บ์คํ
ํด์ ๋ฐ์ดํฐ ํ์ธ
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) decoded;
assertThat(map.get("id")).isEqualTo(1);
assertThat(map.get("name")).isEqualTo("๊น์ง์");
}
@Test
@DisplayName("๋ฆฌ์คํธ ํ์
์์๋ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค")
void testListTypeErasure() {
// Given
List<Employee> originalList = List.of(
new Employee(1L, "๊น์ง์", null),
new Employee(2L, "๋ฐ์ง์", null)
);
byte[] serialized = listTranscoder.encode(originalList);
// ๋ช
์์ TypeReference ์ฌ์ฉ (์ ์)
TypeReference<List<Employee>> explicitTypeRef = new TypeReference<List<Employee>>() {};
List<Employee> decodedWithExplicit = listTranscoder.decodeWithTypeReference(serialized, explicitTypeRef);
// ์ ๋ค๋ฆญ TypeReference ์ฌ์ฉ (ํ์
์๊ฑฐ)
Object decodedWithGeneric = listTranscoder.decodeWithGenericTypeReference(serialized);
// Then
// ๋ช
์์ TypeReference๋ ์ ์ ๋์
assertThat(decodedWithExplicit).hasSize(2);
assertThat(decodedWithExplicit.get(0)).isInstanceOf(Employee.class);
assertThat(decodedWithExplicit.get(0).getName()).isEqualTo("๊น์ง์");
// ์ ๋ค๋ฆญ TypeReference๋ ํ์
์ ๋ณด ์์ค
assertThat(decodedWithGeneric).isInstanceOf(List.class);
@SuppressWarnings("unchecked")
List<Object> genericList = (List<Object>) decodedWithGeneric;
assertThat(genericList).hasSize(2);
// ๋ฆฌ์คํธ ์์๋ค์ด Map์ผ๋ก ์ญ์ง๋ ฌํ๋จ
assertThat(genericList.get(0)).isInstanceOf(Map.class);
assertThat(genericList.get(0)).isNotInstanceOf(Employee.class);
}
@Test
@DisplayName("ํ์
์๊ฑฐ๋ ๊ฐ์ฒด๋ฅผ Employee๋ก ์บ์คํ
ํ๋ฉด ClassCastException์ด ๋ฐ์ํ๋ค")
void testClassCastException() {
// Given
Employee original = new Employee(1L, "๊น์ง์", null);
byte[] serialized = employeeTranscoder.encode(original);
// When
Object decoded = employeeTranscoder.decodeWithGenericTypeReference(serialized);
// Then
assertThrows(ClassCastException.class, () -> {
Employee employee = (Employee) decoded;
});
}
@Test
@DisplayName("์ค์ฒฉ๋ ๊ฐ์ฒด์์๋ ํ์
์ ๋ณด ์์ค์ด ๋ฐ์ํ๋ค")
void testNestedObjectTypeErasure() {
// Given
Employee manager = new Employee(1L, "๊น๋งค๋์ ", null);
Employee employee = new Employee(2L, "๋ฐ์ง์", manager);
byte[] serialized = employeeTranscoder.encode(employee);
// When - ์ ๋ค๋ฆญ TypeReference ์ฌ์ฉ
Object decoded = employeeTranscoder.decodeWithGenericTypeReference(serialized);
// Then
assertThat(decoded).isInstanceOf(Map.class);
@SuppressWarnings("unchecked")
Map<String, Object> empMap = (Map<String, Object>) decoded;
// ์ค์ฒฉ๋ manager ๊ฐ์ฒด๋ Map์ผ๋ก ์ญ์ง๋ ฌํ๋จ
Object managerObj = empMap.get("manager");
assertThat(managerObj).isInstanceOf(Map.class);
assertThat(managerObj).isNotInstanceOf(Employee.class);
@SuppressWarnings("unchecked")
Map<String, Object> managerMap = (Map<String, Object>) managerObj;
assertThat(managerMap.get("name")).isEqualTo("๊น๋งค๋์ ");
}
}
ํ ์คํธ ๊ฒฐ๊ณผ
- ํ์ ์๊ฑฐ ํ์ธ: decodeWithGenericTypeReference() ๋ฉ์๋๋ Employee ๊ฐ์ฒด๊ฐ ์๋ LinkedHashMap์ผ๋ก ์ญ์ง๋ ฌํ๋๋ค.
- ๋ฐํ์ ์ค๋ฅ ๋ฐ์: ํ์ ์๊ฑฐ๋ ๊ฐ์ฒด๋ฅผ Employee๋ก ์บ์คํ ํ๋ ค ํ๋ฉด ClassCastException์ด ๋ฐ์ํ๋ค.
- ์ค์ฒฉ ํ์ ๋ฌธ์ : ๋ฆฌ์คํธ๋ ์ค์ฒฉ๋ ๊ฐ์ฒด์์๋ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฌ, ๋ชจ๋ ๊ฐ์ฒด๊ฐ Map์ผ๋ก ๋ณํ๋๋ค.
- ๋ช ์์ ์ ๋ฌ์ ํ์์ฑ: Class<T>๋ TypeReference<T>๋ฅผ ๋ช ์์ ์ผ๋ก ์ ๋ฌํ๋ ๋ฐฉ์๋ง์ด ์ฌ๋ฐ๋ฅธ ํ์ ์ ๋ณด๋ฅผ ๋ณด์กดํ ์ ์๋ค.
๋ ํผ๋ฐ์ค
- https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
- https://stackoverflow.com/questions/6846244/jackson-and-generic-type-reference
'๐งฑ Back-end' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Redis] Redisson Pub/Sub ๊ธฐ๋ฐ Lock (3) | 2025.03.17 |
|---|---|
| [Redis] Amazon Linux 2023 EC2์ Redis ์ธํ ํ๊ธฐ (0) | 2024.11.25 |