문서를 이해하기 쉽게 내 입장에서 정리해보려한다. 문서 링크는 여기에서 확인 :)
Spring Type Conversion
◼️Converter SPI
타입 변환 로직을 구현한다. 하나의 타입을 다른 타입으로 변환한다.
아래 Converter<S, T> 인터페이스를 구현하여 특정 타입에서 다른 타입으로 변환할 수 있다.
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
아래 예시(String→Integer)처럼 사용 가능하다.
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
◼️ConverterFactory
Converter는 단일 변환(예: String에서 Integer)에 사용되는 반면, ConverterFactory는 계층 구조(예: String에서 다양한 Enum 타입) 전체를 변환하는 데 유용하다.
아래 인터페이스는 S 타입에서 R 타입의 하위 클래스 T로 변환한다.
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
아래 예시 클래스를 보자. convert에서 Enum.valueOf()를 통해 String을 Enum타입으로 변환한다.
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
그럼 아래처럼 문자열을 Enum값으로 변경 가능하다.
ConverterFactory<String, Enum> factory = new StringToEnumConverterFactory();
Converter<String, DayOfWeek> converter = factory.getConverter(DayOfWeek.class); // targetType
DayOfWeek day = converter.convert("MONDAY"); // 문자열 "MONDAY"를 DayOfWeek 열거형 값으로 변환
System.out.println(day); // 출력: MONDAY
◼️GenericConverter
GenericConverter는 복잡한 변환 요구사항을 처리하기 위한 인터페이스이다.
다중 소스 및 대상 타입 간의 변환을 지원한다. 또한, 변환 로직을 구현할 때 사용할 수 있는 컨텍스트를 제공한다.
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes(); //변환가능한 소스타입,타겟 타입 쌍
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
배열을 ArrayList로 바꿔주는 간단한 예시를 보자. 먼저 컨버터를 작성한다.
public class ArrayToCollectionConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() { //어떤타입을 변환가능한지
Set<ConvertiblePair> convertibleTypes = new HashSet<>();
// Object->Collection 을 변환가능타입쌍에 추가
convertibleTypes.add(new ConvertiblePair(Object[].class, Collection.class));
return convertibleTypes;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) return null;
ArrayList<Object> target = new ArrayList<>();
Object[] sourceArray = (Object[]) source;
for (Object item : sourceArray) {
target.add(item);
}
return target;
}
}
그리고 Config에서, 컨버터를 등록하고 FormattingConversionService를 빈 등록후 반환하는 메소드를 작성한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FormattingConversionService conversionService() {
FormattingConversionService conversionService = new FormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new ArrayToCollectionConverter());
}
}
이제 컨버터를 적용해보자! convert에서 array와 반환클래스타입을 넣으면 잘 변환됨을 확인 할 수 있다.
@RestController
@Slf4j
public class ArrayToCollectionService {
@Autowired
private ConversionService conversionService;
@GetMapping("/array-to")
public void convertArrayToCollection() {
String[] array = {"one", "two", "three"};
Collection<?> converted = conversionService.convert(array, Collection.class);
log.info("Converted collection: {}", converted);
log.info("Converted collection type: {}", converted.getClass());
for (Object element : converted) { // 각 원소 내용 출력
log.info("Element: {}",element);
}
}
}
◼️ConditionalGenericConverter
ConditionalGenericConverter는 특정 조건이 충족될 때만 변환을 수행하는 인터페이스이다.
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); //변환조건 확인
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
예를들어 아래는 sourceType이 Long타입이고,targetType이 @Entity애노테이션이 있을때만 변환한다.
(아래코드에서 변환은 ID를 사용해 DB에서 엔티티를 찾고 반환하는 과정)
public class IdToEntityConverter implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return sourceType.getType() == Long.class && targetType.getType().isAnnotationPresent(Entity.class);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> convertibleTypes = new HashSet<>();
convertibleTypes.add(new ConvertiblePair(Long.class, MyEntity.class));
return convertibleTypes;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Long id = (Long) source;
return findEntityById(id, targetType.getType());
}
private Object findEntityById(Long id, Class<?> entityType) {
// JPA 데이터베이스에서 엔티티 검색
return entityManager.find(entityType, id);
}
}
위와마찬가지로 컨버터를 등록하고 활용할 수 있다.
@Data
@Entity
public class MyEntity {
@Id
private Long id;
private String name;
}
@Service
public class EntityService {
@Autowired
private ConversionService conversionService;
public void convertIdToEntity() {
Long entityId = 1L;
MyEntity entity = (MyEntity) conversionService.convert(entityId, MyEntity.class);
System.out.println("Converted Entity: " + entity.getName());
}
}
'Spring' 카테고리의 다른 글
[Spring] 스프링 DB 1편 - 데이터 접근 핵심 원리 섹션1 JDBC 이해 (0) | 2024.05.28 |
---|---|
[Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 섹션11 스프링 파일 업로드 (0) | 2024.05.21 |
[Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 섹션10 스프링 타입 컨버터 (0) | 2024.05.18 |
[Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 섹션9 API 예외 처리 (0) | 2024.05.14 |
[Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 섹션8 예외 처리와 오류 페이지 (0) | 2024.05.12 |