preface
Like data binding, type conversion is also one of the core features of Spring. The initial configuration information of Spring mainly exists in the form of XML, which requires Spring to convert string configuration into specific Java types. After the evolution of multiple versions, the type conversion function in Spring is becoming more and more mature.
PropertyEditor
The original purpose of Spring type conversion is to convert a string to the type corresponding to the bean's constructor parameters or properties. A PropertyEditor has been provided in the java bean specification for setting and obtaining properties, so Spring originally directly reused the PropertyEditor for type conversion.
PropertyEditor basic usage
The PropertyEditor interface is defined as follows.
public interface PropertyEditor { // Property setting get method void setValue(Object value); Object getValue(); String getAsText(); void setAsText(String text) throws java.lang.IllegalArgumentException; ...Omit support GUI Other methods of program }
PropertyEditor mainly provides support for GUI programs, so there are other methods besides property setting and obtaining methods. So how is this interface used? Suppose we need to convert a string to an integer, the code is as follows.
public class String2IntegerPropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { this.setValue(Integer.valueOf(text)); } } public class App { public static void main(String[] args) { String text = "520"; String2IntegerPropertyEditor editor = new String2IntegerPropertyEditor(); editor.setAsText(text); Integer number = (Integer) editor.getValue(); System.out.println(number); } }
When you customize PropertyEditor, you can inherit the PropertyEditorSupport class, then rewrite the #setAsText method, complete the transformation of the string in this method, and call #setValue to store the converted value, then complete the transformation and call the #getValue method to get the value after the transformation type.
Spring default PropertyEditor
Spring's default property editor is located in the package org springframework. beans. In property editors, in the first part Talk about data binding in Spring core features We have mentioned that the properties of XML configuration are set to the object through BeanWrapper. The implementation class used is BeanWrapperImpl. When this class is instantiated, the default PropertyEditor will be activated.
If Spring cannot find the built-in default PropertyEditor during type conversion, Spring will also find the default PropertyEditor according to certain policies. Specifically, find the PropertyEditor with the name of type Editor. If you want to convert String to com zzuhkp. User, the fully qualified name of the default PropertyEditor to be found is com zzuhkp. UserEditor.
Custom PropertyEditor in Spring
Add PropertyEditor to BeanFactory
When Spring parses the property value of the configuration setting bean in XML, instantiating BeanWrapperImpl and activating the default PropertyEditor will initialize the custom PropertyEditor in BeanFactory to BeanWrapperImpl. During type conversion, the custom PropertyEditor will take precedence over the default PropertyEditor.
So how to add a custom PropertyEditor to BeanFactory?
- If you can get an instance of ConfigurableBeanFactory, the most direct method is to call the ConfigurableBeanFactory #registercustomeeditor method to set it directly.
- Another way is to use the beanfactoryprocessor callback provided by the ApplicationContext lifecycle to obtain the BeanFactory instance and register the custom PropertyEditor.
@Component public class CustomEditorProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { factory.registerCustomEditor(Integer.class, String2IntegerPropertyEditor.class); } }
Add PropertyEditorRegistrar to BeanFactory
In addition to adding the custom PropertyEditor in BeanFactory to BeanWrapperImpl, Spring also uses the propertyeditorregister in BeanFactory to add a new PropertyEditor for BeanWrapperImpl. The PropertyEditorRegistrar interface is defined as follows.
public interface PropertyEditorRegistrar { void registerCustomEditors(PropertyEditorRegistry registry); }
The registry in the interface method parameter is the instance of BeanWrapperImpl. Spring will call back the method in PropertyEditorRegistrar after initializing BeanWrapperImpl. ApplicationContext uses this mechanism to add an instance of propertyeditorregister resourceeditorregister to BeanFactory during refresh, and customize some property editors related to resources.
To add propertyeditorregister to BeanFactory, you can call the configurablebeanfactory #addpropertyeditorregister method. In this way, the custom PropertyEditor code is as follows.
@Component public class CustomEditorRegistrar implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { factory.addPropertyEditorRegistrar(new PropertyEditorRegistrar() { @Override public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) { propertyEditorRegistry.registerCustomEditor(Integer.class, new String2IntegerPropertyEditor()); } }); } }
Spring built-in class CustomEditorRegistrar
Add custom ProprtyEditor and customeditorregister to BeanFactory. Spring also has a built-in implementation class CustomEditorConfigurer of beanfactoryprocessor, which is used as follows.
@Configuration public class Config { @Bean public CustomEditorConfigurer customEditorConfigurer() { CustomEditorConfigurer configurer = new CustomEditorConfigurer(); configurer.setCustomEditors(Collections.singletonMap(Integer.class, String2IntegerPropertyEditor.class)); configurer.setPropertyEditorRegistrars(new PropertyEditorRegistrar[]{ new PropertyEditorRegistrar() { @Override public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(Integer.class, new String2IntegerPropertyEditor()); } } }); return configurer; } }
ConversionService
Although PropertyEditor can already support type conversion, due to the insufficient single responsibility of PropertyEditor (including java bean events and GUI support in addition to type conversion) and the limitation of source type (only String), Spring proposed a new type conversion interface ConversionService in version 3.0.
This interface is defined as follows.
public interface ConversionService { // Can the source type be converted to the target type boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType); boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType); // Type conversion <T> T convert(@Nullable Object source, Class<T> targetType); Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType); }
The interface contains two types of methods. One is used to judge whether the source type can be converted to the target type, and the other is used to convert the source type to the target type. TypeDescriptor is used to describe the converted type information to support generics.
Custom ConversionService
In the spring standard environment, ConversionService is not configured by default, but spring provides a default implementation of DefaultConversionService. If you need to use ConversionService to replace PropertyEidtor's conversion from String to bean property types, you can use BeanFactory to directly set ConversionService, You can also configure a ConversionService type bean named ConversionService in the application context. Spring will set this bean to BeanFactory when refreshing the context, and finally to BeanWrapperImpl instance.
// Method 1: directly set ConversionService @Component public class ConversionServiceProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.setConversionService(new DefaultConversionService()); } } // Method 2: register a ConversionService bean named conversionService @Configuration public class Config { @Bean public ConversionService conversionService(){ return new DefaultConversionService(); } }
Add Converter to ConversionService
Although the purpose of replacing the PropertyEditor can be achieved by configuring ConversionService to BeanFactory, ConversionService is not omnipotent. For example, it is certainly impossible to convert a User class instance into an Order class.
Converter
The default ConversionService implementation does not directly perform type conversion internally, but delegates the type conversion work to the Converter interface. The implementation class of this interface completes the specific type conversion work. Defaultconversionservice has built-in Converter instances and provides some methods to add custom Converter interfaces.
Let's first look at how the Converter is defined.
public interface Converter<S, T> { T convert(S source); }
Converter is a functional interface with only one internal method for type conversion, where generic S represents the source type and generic T represents the target type.
ConverterRegistry
How to add a custom Converter to the ConversionService? The defaultconversionservice implements the interface ConverterRegistry, which defines some methods for registering converters, as follows.
public interface ConverterRegistry { void addConverter(Converter<?, ?> converter); <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter); void addConverter(GenericConverter converter); void addConverterFactory(ConverterFactory<?, ?> factory); void removeConvertible(Class<?> sourceType, Class<?> targetType); }
The ConverterRegistry interface provides methods to add converters and remove converters of a given type. Here are two interfaces that we haven't mentioned, namely GenericConverter and ConverterFactory.
GenericConverter
The Converter can only convert one type to another. In order to support multiple types of conversion, GenericConverter appears. This interface is defined as follows.
public interface GenericConverter { // Gets the convertible type Set<ConvertiblePair> getConvertibleTypes(); // Type conversion Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; ...Omit some codes } }
There is a ConvertiblePair class inside GenericConverter that holds the source type and target type. In addition to the type conversion method, it also provides a method to obtain convertible type pairs.
ConverterFactory
As for ConverterFactory, it is the factory of Converter. With this factory, ConversionService can obtain the Converter. The ConverterFactory interface is defined as follows.
public interface ConverterFactory<S, R> { <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
The generic S in the interface represents the source type and T represents the target type.
ConditionalConverter
At this point, we can add a custom Converter to ConversionService, but we should also mention the ConditionalConverter interface, which is used to determine whether type conversion can be performed before type conversion.
The ConditionalConverter interface is defined as follows.
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); }
In addition, Spring also integrates GenericConverter and ConditionalConverter, providing a ConditionalGenericConverter interface.
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter { }
Add Converter to Spring standard environment
After understanding various converters, we can add the custom Converter to ConversionServcie. The example code is as follows.
public class String2IntegerConverter implements ConditionalGenericConverter { @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return sourceType.getType().equals(String.class) && targetType.getType().equals(Integer.class); } @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, Integer.class)); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return Integer.valueOf(source.toString()); } } @Configuration public class Config { @Bean public ConversionService conversionService() { DefaultConversionService conversionService = new DefaultConversionService(); conversionService.addConverter(new String2IntegerConverter()); return conversionService; } }
Add Converter in Spring Web Environment
In the standard environment, the ConversionService bean named conversionService can only be used to set bean properties after bean instantiation. In the Web environment, Spring needs to convert the information in the request into controller method parameters, which also needs type conversion.
The configuration of the custom Converter in the Web environment is different from that in the standard environment. We need to define a configuration class that implements WebMvcConfigurationSupport, and then override #addFormatters method. The details are as follows.
@Configuration public class Config extends WebMvcConfigurationSupport { @Override protected void addFormatters(FormatterRegistry registry) { registry.addConverter(new String2IntegerConverter()); } }
FormatterRegistry is an interface that inherits the ConverterRegistry interface.
Spring type conversion process
Standard environment bean property setting type conversion process
Some Spring type conversion processes have been summarized above. Here, a diagram is drawn according to the process to intuitively understand the Spring type conversion process, as shown in the figure below.
Web environment controller method parameter type conversion process
The type conversion of Web environment is mainly used to convert the request to the conoller method parameter. The type conversion here only uses ConversionService. However, Spring has configured one for us in the WebMvcConfigurationSupport class and provided an addFormatters method to add a Converter, The trace code can see that the Web environment type conversion has the following process.