|
This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.12! |
Spring Type Conversion
The core.convert package provides a general type conversion system. The system defines
an SPI to implement type conversion logic and an API to perform type conversions at
runtime. Within a Spring container, you can use this system as an alternative to
PropertyEditor implementations to convert externalized bean property value strings to
the required property types. You can also use the public API anywhere in your application
where type conversion is needed.
Converter SPI
The SPI to implement type conversion logic is simple and strongly typed, as the following interface definition shows:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
To create your own converter, implement the Converter interface and parameterize S
as the type you are converting from and T as the type you are converting to. You can also transparently apply such a
converter if a collection or array of S needs to be
converted to an array or collection of T, provided that a delegating array or collection
converter has been registered as well (which DefaultConversionService does by default).
For each call to convert(S), the source argument is guaranteed to not be null. Your
Converter may throw any unchecked exception if conversion fails. Specifically, it should throw an
IllegalArgumentException to report an invalid source value.
Take care to ensure that your Converter implementation is thread-safe.
Several converter implementations are provided in the core.convert.support package as
a convenience. These include converters from strings to numbers and other common types.
The following listing shows the StringToInteger class, which is a typical Converter implementation:
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
Using ConverterFactory
When you need to centralize the conversion logic for an entire class hierarchy
(for example, when converting from String to Enum objects), you can implement
ConverterFactory, as the following example shows:
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
Parameterize S to be the type you are converting from and R to be the base type defining
the range of classes you can convert to. Then implement getConverter(Class<T>),
where T is a subclass of R.
Consider the StringToEnumConverterFactory as an example:
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());
}
}
}
Using GenericConverter
When you require a more sophisticated Converter implementation, consider using the
GenericConverter interface. With a more flexible but less strongly typed signature than
Converter, a GenericConverter supports converting between multiple source and target
types. In addition, a GenericConverter is provided source and target type descriptors
that you can use when you implement your conversion logic. Such type descriptors enable
type conversion to be driven by an annotation on the source of the descriptor (such as a
field or method) or by generic information declared in a field signature, method
signature, etc. The following listing shows the definition of the GenericConverter
interface:
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
To implement a GenericConverter, have getConvertibleTypes() return the supported
source → target type pairs. Then implement convert(Object, TypeDescriptor,
TypeDescriptor) to contain your conversion logic. The source TypeDescriptor provides
access to the source field or method that holds the value being converted. The target
TypeDescriptor provides access to the target field or method where the converted value
is to be set.
A good example of a GenericConverter is a converter that converts between a Java array
and a collection. Such an ArrayToCollectionConverter introspects the field or method
that declares the target collection type to resolve the collection’s element type. This
lets each element in the source array be converted to the collection element type before
the collection is set on the target field or supplied to the target method or constructor.
Because GenericConverter is a more complex SPI interface, you should use
it only when you need it. Favor Converter or ConverterFactory for basic type
conversion needs.
|
Using ConditionalGenericConverter
Sometimes, you want a Converter to run only if a specific condition holds true. For
example, you might want to run a Converter only if a specific annotation is present on
the target field or method, or you might want to run a Converter only if a specific
method (such as a static valueOf method) is defined on the target type.
ConditionalGenericConverter is the union of the GenericConverter and
ConditionalConverter interfaces that lets you define such custom matching criteria:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
A good example of a ConditionalGenericConverter is an IdToEntityConverter that converts
between a persistent entity identifier and an entity reference. Such an IdToEntityConverter
might match only if the target entity type declares a static finder method (for example,
findAccount(Long)). You might perform such a finder method check in the implementation of
matches(TypeDescriptor, TypeDescriptor).
The ConversionService API
ConversionService defines a unified API for executing type conversion logic at
runtime. Converters are often run behind the following facade interface:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
Most ConversionService implementations also implement ConverterRegistry, which
provides an SPI for registering converters. Internally, a ConversionService
implementation delegates to its registered converters to carry out type conversion logic.
A robust ConversionService implementation is provided in the core.convert.support
package. GenericConversionService is the general-purpose implementation suitable for
use in most environments. ConversionServiceFactory provides a convenient factory for
creating common ConversionService configurations.
Configuring a ConversionService
A ConversionService is a stateless object designed to be instantiated at application
startup and then shared between multiple threads. In a Spring application, you typically
configure a ConversionService instance for each Spring container (or ApplicationContext).
Spring picks up that ConversionService and uses it whenever type
conversion needs to be performed by the framework. You can also inject this
ConversionService into any of your beans and invoke it directly.
If no ConversionService is registered with Spring, the original PropertyEditor-based
system is used.
|
To register a default ConversionService with Spring, add the following bean definition
with an id of conversionService:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
A default ConversionService can convert between strings, numbers, enums, collections,
maps, and other common types. To supplement or override the default converters with your
own custom converters, set the converters property. Property values can implement
any of the Converter, ConverterFactory, or GenericConverter interfaces.
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
It is also common to use a ConversionService within a Spring MVC application. See
Conversion and Formatting in the Spring MVC chapter.
In certain situations, you may wish to apply formatting during conversion. See
The FormatterRegistry SPI
for details on using FormattingConversionServiceFactoryBean.
Using a ConversionService Programmatically
To work with a ConversionService instance programmatically, you can inject a reference to
it like you would for any other bean. The following example shows how to do so:
-
Java
-
Kotlin
@Service
public class MyService {
private final ConversionService conversionService;
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
For most use cases, you can use the convert method that specifies the targetType, but it
does not work with more complex types, such as a collection of a parameterized element.
For example, if you want to convert a List of Integer to a List of String programmatically,
you need to provide a formal definition of the source and target types.
Fortunately, TypeDescriptor provides various options to make doing so straightforward,
as the following example shows:
-
Java
-
Kotlin
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)), (1)
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class))); (2)
| 1 | List<Integer> type descriptor |
| 2 | List<String> type descriptor |
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(Integer::class.java)), (1)
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java))) (2)
| 1 | List<Integer> type descriptor |
| 2 | List<String> type descriptor |
Note that DefaultConversionService automatically registers converters that are
appropriate for most environments. This includes collection converters, scalar
converters, and basic Object-to-String converters. You can register the same converters
with any ConverterRegistry by using the static addDefaultConverters
method on the DefaultConversionService class.
Converters for value types are reused for arrays and collections, so there is
no need to create a specific converter to convert from a Collection of S to a
Collection of T, assuming that standard collection handling is appropriate.