spring 6.2.4 버전 이후의 코드를 기준으로 작성하였습니다.
GenericConversionService
: 대부분의 환경에 적합한ConversionService
의 기본 구현입니다.ConfigurableConversionService
인터페이스 등록 API를 통해ConverterRegistry
를 간접적으로 구현하고 있습니다.ConversionService
: 타입 변환을 위한 인터페이스입니다. 이것이 변환 시스템의 진입점입니다. 이 시스템의convert(Object, Class)
사용하여 스레드 안전 타입 변환을 수행할 수 있습니다.ConverterRegistry
: 타입 변환 시스템을 변환기에 등록하기 위해 사용합니다.ConfigurableConversionService
: 모든 변환 서비스 유형은 아니더라도 대부분의 전환 서비스 유형에서 구현할 구성 인터페이스입니다. 변환 서비스에서 노출되는 읽기 전용 작업과 변환 레지스트리의 변경 작업을 통합하여 변환기를 편리하게 임시로 추가 및 제거할 수 있습니다. 후자는 애플리케이션 컨텍스트 부트스트랩 코드에서 구성 가능한 환경 인스턴스에 대해 작업할 때 특히 유용합니다.
사용 예시
ApplicationServletEnvironment
스프링 실행을 위한 설정한 환경 값을 적절히 변환하기 위해 ConversionService
를 사용합니다. 예를 들어 spring.application.name=debug
와 같은 값은 ApplicationServletEnvironment
을 통해 사용할 수 있습니다. 이때 getProperty
메서드를 확인해보면 key
에 해당하는 값을 String
타입으로 변환하여 조회하는 것을 확인할 수 있습니다.
// AbstractEnvironment
@Override
@Nullable
public String getProperty(String key) {
return this.propertyResolver.getProperty(key);
}
// ConfigurationPropertySourcesPropertyResolver
@Override
public String getProperty(String key) {
return getProperty(key, String.class, true);
}
// ConfigurationPropertySourcesPropertyResolver
private <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
// ...
try {
return convertValueIfNecessary(value, targetValueType);
}
}
// AbstractPropertyResolver
@Nullable
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
// ..
ConversionService conversionServiceToUse = this.conversionService;
// ..
return conversionServiceToUse.convert(value, targetType);
}
GenericConversionService에서 변환 과정
// GenericConversionService
@Override
public @Nullable Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
return handleConverterNotFound(source, sourceType, targetType);
}
getConverter
:sourceType
과targetType
을 기준으로 변환 가능한 컨버터를 찾습니다.sourceType
: 원본,targetType
: 변환하려는 타입
ConversionUtils.invokeConverter(converter, source, sourceType, targetType)
: 찾은 컨버터를 통해source
를targetType
으로 변환합니다.
적합한 컨버터 찾기
// GenericConversionService
protected @Nullable GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
GenericConverter converter = this.converterCache.get(key);
if (converter != null) {
return (converter != NO_MATCH ? converter : null);
}
converter = this.converters.find(sourceType, targetType);
if (converter == null) {
converter = getDefaultConverter(sourceType, targetType);
}
if (converter != null) {
this.converterCache.put(key, converter);
return converter;
}
this.converterCache.put(key, NO_MATCH);
return null;
}
public @Nullable GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Search the full type hierarchy
List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
for (Class<?> sourceCandidate : sourceCandidates) {
for (Class<?> targetCandidate : targetCandidates) {
ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
if (converter != null) {
return converter;
}
}
}
return null;
}
private @Nullable GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
TypeDescriptor targetType, ConvertiblePair convertiblePair) {
// Check specifically registered converters
ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
if (convertersForPair != null) {
GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
if (converter != null) {
return converter;
}
}
// Check ConditionalConverters for a dynamic match
for (GenericConverter globalConverter : this.globalConverters) {
if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
return globalConverter;
}
}
return null;
}
// ConvertersForPair
public @Nullable GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Look for proper match among all converters (taking full generics into account)
for (GenericConverter converter : this.converters) {
if (!(converter instanceof ConditionalGenericConverter genericConverter) ||
genericConverter.matches(sourceType, targetType)) {
return converter;
}
}
// Fallback to pre-6.2.3 behavior: accept Class match for unresolvable generics
for (GenericConverter converter : this.converters) {
if (converter instanceof ConverterAdapter converterAdapter &&
converterAdapter.matchesFallback(sourceType, targetType)) {
return converter;
}
}
return null;
}
컨버터를 찾을 때는 sourceType
를 구성하고 있는 타입 계층과 targetType
을 구성하고 있는 타입 계층을 조합하여 등록된 컨버터를 조회합니다.
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Check raw type first...
if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
return false;
}
// Full check for complex generic type match required?
ResolvableType rt = targetType.getResolvableType();
if (!(rt.getType() instanceof Class) && !rt.isAssignableFromResolvedPart(this.targetType)) {
return false;
}
return !(this.converter instanceof ConditionalConverter conditionalConverter) ||
conditionalConverter.matches(sourceType, targetType);
}
public boolean matchesFallback(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (this.typeInfo.getTargetType() == targetType.getObjectType() &&
this.targetType.hasUnresolvableGenerics() &&
(!(this.converter instanceof ConditionalConverter conditionalConverter) ||
conditionalConverter.matches(sourceType, targetType)));
}
matches
와 matchesFallback
에서는 컨버터의 this.targetType
이 변환하려는 targetType
에 할당 가능한지 확인합니다.
컨버터를 통한 변환
// ConversionUtils
@Nullable
public static Object invokeConverter(GenericConverter converter, @Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return converter.convert(source, sourceType, targetType);
}
catch (ConversionFailedException ex) {
throw ex;
}
catch (Throwable ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
}
ConversionUtils
의 invokeConverter
에서 컨버터를 활용해 변환을 시도합니다.
- 참고 공식 문서
GenericConversionService
: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/support/GenericConversionService.htmlConversionService
: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/ConversionService.htmlConverterRegistry
: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/converter/ConverterRegistry.htmlConfigurableConversionService
: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/support/ConfigurableConversionService.html
'스프링' 카테고리의 다른 글
spring-framework 34685 이슈 (0) | 2025.04.16 |
---|---|
Entity Callbacks 실행 과정 (0) | 2025.04.10 |
Entity Callbacks 공식 문서 정리 (0) | 2025.04.09 |
InsertOnlyProperty (0) | 2025.04.09 |
MVC의 요청 처리 과정 (0) | 2025.03.31 |