woniper

SuperTypeToken #1 TypeToken(ModelMapper)과 ParameterizedTypeReference(Spring) 사용법 본문

Java

SuperTypeToken #1 TypeToken(ModelMapper)과 ParameterizedTypeReference(Spring) 사용법

woniper1 2016. 11. 12. 20:55

  토비님 방송을 보고 SuperTypeToken이란 것을 알게됐다. 이게 뭔지도 모르고 써서 그렇지 그동안 종종 써오던 코드다.

SuperTypeToken 이란게 무엇인지는 간단하게 설명하고 그동안 용어도 모른체 사용해온 코드를 설명하기 위해서 글을 쓴다.

일단 자세히 알고 싶은 분은 여기를 보기 바란다.


SuperTypeToken

  자바에서 ArrayList로 제네릭을 사용할 때는 아래와 같이 사용한다.

1
List<String> list = new ArrayList<>();
cs


그리고 클래스에 메타정보를 가지고 있는 java.lang.Class 타입이 있다. 자바에 모든 클래스는 이 Class 타입이 있다. String의 Class를 가져오기 위해서는 아래와 같다.

1
Class<String> stringClazz = String.class;
cs


그럼 java.util.List에 Class 타입을 가져오기 위해서는 어떻게 해야할까? 마찬가지로 아래와 같다.

1
Class<List> stringClazz = List.class;
cs


그런데 java.util.List는 제네릭을 사용할 수 있다. 즉 List<String> 이렇게 선언된 List를 흔히 String List라고 부른다. 그렇다면 String List Type에 java.lang.Class는 어떻게 가져와야 할까?

1
Class<List<String>> listClazz = List<String>.class;
cs

이렇게 가져와야 할까? 코드를 직접 ide에 타이핑해보면 알 수 있다. 이건 문법적으로 오류다. 즉, 컴파일 에러인 것이다. 이렇게 사용할 수 없다.


그런데 java.util.List<java.lang.String>.class와 같이 사용하고 싶은 경우가 발생한다.

예를 들면

  1.  A 객체를 B 객체로 매핑 (ModelMapper 사용)
  2. Spring에 RestTemplate 사용 시
A 객체를 B 객체로 매핑
  예를 들어 아래와 같이 UserEntity 클래스와 UserDto 클래스가 있다고 가정하자.
우리는 UserEntity 객체를 UserDto 객체로 데이터를 매핑하고 싶은 것이다.  UserEntity.username과 UserEntity.age 값을 UserDto.username과 UserDto.age에 그대로 복사하고 싶은 것이다. 객체 복사가 아니라 필드값 복사를 의미한다.

UserEntity
1
2
3
4
5
6
public class UserEntity {
    private String username;
    private int age;
 
    // getter and setter
}
cs


UserDto

1
2
3
4
5
6
7
public class UserDto {
    private String username;
    private int age;
 
    // getter and setter
}
 
cs


여기서 필드값을 복사 할 수 있는 방법 2가지를 설명할 것이다. 간단하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import org.junit.Before;
import org.junit.Test;
import org.modelmapper.ModelMapper;
 
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
 
public class UserMappingTest {
 
    private UserEntity user;
 
    @Before
    public void setUp() throws Exception {
        user = new UserEntity();
        user.setUsername("woniper");
        user.setAge(28);
    }
 
    @Test
    public void testSetterMapping() throws Exception {
        // given
        UserDto userDto = new UserDto();
 
        // when
        userDto.setUsername(user.getUsername());
        userDto.setAge(user.getAge());
 
        // then
        assertUser(user, userDto);
    }
 
    @Test
    public void testModelMapperMapping() throws Exception {
        // given
        ModelMapper mapper = new ModelMapper();
        UserDto userDto = new UserDto();
 
        // when
        mapper.map(user, userDto);
 
        // then
        assertUser(user, userDto);
    }
 
    private void assertUser(UserEntity entity, UserDto dto) {
        assertThat(entity.getUsername(), is(dto.getUsername()));
        assertThat(entity.getAge(), is(dto.getAge()));
    }
}
cs


  단순하다 Setter 메소드를 통해 데이터를 복사하는 방법과 ModelMapper라는 라이브러리를 사용한 복사다.

이것은 객체에 데이터를 복사하는 경우를 설명하기 위함이다.

  내가 진짜 설명하고 싶은 것은 List<UserEntity>를 List<UserDto>로 데이터를 복사하고 싶은것이다. List를 복사하는 방법도 마찬가지로 간단하다. 반복문을 돌려 Setter로 복사하는 방법과 ModelMapper로 복사하는 방법이다. 그런데, ModelMapper는 반복문을 사용하지 않고 List를 그대로 복사하는 방법이 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import org.junit.Test;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
 
public class UserListMapping {
 
    @Test
    public void testMapping() throws Exception {
        // given
        List<UserEntity> users = createUserEntityList();
        ModelMapper mapper = new ModelMapper();
 
        // when
        List<UserDto> userDtos = mapper.map(users, new TypeToken<List<UserDto>>(){}.getType());
 
        // then
        assertEquals(users.size(), userDtos.size());
        for (int i = 0; i < users.size(); i++) {
            UserEntity entity = users.get(i);
            UserDto dto = userDtos.get(i);
            assertThat(entity.getUsername(), is(dto.getUsername()));
            assertThat(entity.getAge(), is(dto.getAge()));
        }
    }
 
    @Test
    public void testFailMapping() throws Exception {
        // given
        List<UserEntity> users = createUserEntityList();
        List<UserDto> userDtos = new ArrayList<>();
        ModelMapper mapper = new ModelMapper();
 
        // when
        mapper.map(users, userDtos);
 
        // then
        assertFalse(users.size() == userDtos.size());
    }
 
    private List<UserEntity> createUserEntityList() {
        List<UserEntity> users = new ArrayList<>();
 
        for (int i = 0; i < 5; i++) {
            users.add(new UserEntity("woniper" + i, 28));
        }
 
        return users;
    }
}
 
cs


  testFailMapping test 코드를 보자. 앞서 예제인 mapper.map(Object, Object)로 매핑했더니 데이터 복사가 안된다.

testMapping test 코드는 TypeToKen이라는 클래스를 사용해 List<String>에 Type을 알아냈다. 이 메소드는 map(Object, java.lang.reflect.Type)이며, return 값도 있다. 데이터 복사에 성공했다.

왜 이런 결과가 나왔을까?

  map(Object, Object) 메소드 경우에는 List<String>인 String List에 타입을 가져오지 못해 매핑하지 못한 것이다. 반면 map(Object, java.lang.reflect.Type)은 ModelMapper 라이브러리에서 제공하는 TokenType이라는 클래스를 통해 String List에 타입을 가져 올 수 있기 때문이다.


ParameterizedTypeReference

  Spring 프레임워크에도 ModelMapper 라이브러리와 같이 Type을 가져올 수 있는 클래스를 제공한다. 바로 ParameterizedTypeReference이다. 아래 테스트 코드를 참고하자. 사용 방법은 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package net.woniper.token;
 
import org.junit.Test;
import org.modelmapper.TypeToken;
import org.springframework.core.ParameterizedTypeReference;
 
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
 
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
 
public class SuperTypeTokenTest {
 
    private final static String STRING_TYPE =               "java.lang.String";
    private final static String LIST_TYPE =                 "java.util.List";
    private final static String STRING_LIST_TYPE =          "java.util.List<java.lang.String>";
    private final static String STRING_OBJECT_MAP_TYPE =    "java.util.Map<java.lang.String, java.lang.Object>";
 
    @Test
    public void testModelMapper() throws Exception {
        assertModelMapperTypeToken(STRING_TYPE, new TypeToken<String>(){});
        assertModelMapperTypeToken(LIST_TYPE, new TypeToken<List>(){});
        assertModelMapperTypeToken(STRING_LIST_TYPE, new TypeToken<List<String>>(){});
        assertModelMapperTypeToken(STRING_OBJECT_MAP_TYPE, new TypeToken<Map<String, Object>>(){});
    }
 
    @Test
    public void testSpringParameterizedTypeReference() throws Exception {
        assertParameterizedTypeReference(STRING_TYPE, new ParameterizedTypeReference<String>() {});
        assertParameterizedTypeReference(LIST_TYPE, new ParameterizedTypeReference<List>() {});
        assertParameterizedTypeReference(STRING_LIST_TYPE, new ParameterizedTypeReference<List<String>>() {});
        assertParameterizedTypeReference(STRING_OBJECT_MAP_TYPE, new ParameterizedTypeReference<Map<String, Object>>() {});
    }
 
    private void assertModelMapperTypeToken(String expected, TypeToken<?> typeToken) {
        assertSuperTypeToken(expected, typeToken.getType());
    }
 
    private void assertParameterizedTypeReference(String expected, ParameterizedTypeReference<?> typeReference) {
        assertSuperTypeToken(expected, typeReference.getType());
    }
 
    private void assertSuperTypeToken(String expected, Type type) {
        assertThat(type.getTypeName(), is(expected));
    }
}
 
cs


  아참 중요한 설명을 빼먹을뻔했다. ParameterizedTypeReference 클래스와 TypeToken 클래스를 사용해 Type을 얻기 위해서는 Local Class로 생성해야한다.

1
2
new TypeToken<String>(){};
new ParameterizedTypeReference<String>() {};
cs

  이렇게말이다. 이부분에 대한 자세한 설명은 토비님 동영상을 참고하기 바란다. 사실 내가 설명하는 것은 앞서 말했듯이 모르고 사용하던 ModelMapper에 TypeToken이라는 것을 용어도 모른체 사용하고 있었기 때문에 이를 비교 설명하기 위해서다.

음.. 오늘 포스팅은 여기서 마무리 하고 다음은 TypeToken과 ParameterizedTypeReference 코드를 까보며 설명할 예정이다.

Comments