Spring Type conversion of

I've been asked about this in interviews before spring Data binding issues , It had always been a hazy concept , Recently, I had some time to take a look at the data transformation , Keep a record of the content .

Here's how to use it , Then put some personal notes to see the parameter binding source code , Probably because the strength is not enough , Some things are not quite right , Please point out any errors .

ConversionService

Native Java There is a tool that provides data conversion capability ——PropertyEditor. But its function is limited , It can only convert a string to one Java object . stay web In the project , If you just look at the part that interacts with the front end , That's enough . But inside the back end of the project, things have to be redone .

Spring We designed it for this problem Converter modular , It is located in org.springframework.core.converter In bag . This module is sufficient to replace the native one PropertyEditor, however spring I chose to support both , stay Spring MVC This is used when dealing with parameter bindings .

The core of the module is ConversionService Interface , The contents are as follows :

public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
@Nullable
<T> T convert(@Nullable Object source, Class<T> targetType); @Nullable
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType); }

The methods defined in the interface are fairly straightforward , See the name and know the meaning . Among them TypeDescriptor yes spring Self - defined classes , It provides a convenient way to get more information about types . Such as whether there are annotations 、 Whether to implement map Interface 、 obtain map Of key And value Of TypeDescriptor wait .

thus it can be seen ,converter Modules not only support conversions between arbitrary types , And it's easier to get more type information and do more detailed type conversions .

converter

ConversionService Just a Service, Operations for each type cast , It is not the ultimate operator , It will give the operation to the converter of the corresponding type . In the real world , Due to the complexity of the business , There are also different requirements for type conversions , therefore spring Several interfaces are provided to facilitate custom converters .

Converter<S, T>

The interface is defined as follows :

@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S var1);
}

The interface is very simple , Only one transformation method is defined , Two generic parameters are the two types that need to be converted . This is preferred when working with two types of conversions separately , The one on one , But if you have the same parent class ( Or interface ) Type conversion is required , Write one for each type Converter It's obviously very irrational . In this case ,spring Provides a ConverterFactory Interface .

ConverterFactory<S, R>

public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> var1);
}

We can see , The factory method can be produced from S Type to T Converter of type , and T A type must be inherited or implemented R type , We can call it visually “ One to many ”, Therefore, this interface is more suitable for implementing converters that need to be converted to the same type .

The above two interfaces are sufficient for most requirements ( At least I think so ), But not yet TypeDescriptor Do you ? If you want to implement more complex transformations ,spring Offered to own TypeDescriptor Parametric GenericConverter Interface .

GenericConverter

public interface GenericConverter {
@Nullable
Set<ConvertiblePair> getConvertibleTypes(); @Nullable
Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(sourceType, "Source type must not be null");
Assert.notNull(targetType, "Target type must not be null");
this.sourceType = sourceType;
this.targetType = targetType;
} public Class<?> getSourceType() {
return this.sourceType;
} public Class<?> getTargetType() {
return this.targetType;
} // Save some Override Method
}
}

GenericConverter Has an inner class ConvertiblePair, The purpose of this inner class is simply to encapsulate the source and target types of the transformation .

about GenericConverter,getConvertibleTypes Method returns the conversion type supported by the converter ( one-on-one , One to many , Many to many can be satisfied ),convert The method handles the specific transformation logic as before .

and , If you feel that it is not enough for a converter just to determine whether the source type and the target type are consistent or not ,Spring Another interface is provided ConditionalGenericConverter.

ConditionalGenericConverter

public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

ConditionalGenericConverter Interface inherited GenericConverter and ConditionalConverter Interface , stay matches The method can then determine whether to support the transformation based on the fact that the source type and the target type have been matched .

Spring The official implementation ConditionalGenericConverter Converters for interfaces are mostly used to handle conversions involving collections or arrays , Of these matches Method is used to determine whether an element in a collection or array can be successfully converted . And because GenericConverter And ConditionalGenericConverter Interface functions are too similar , Just do it ConditionalGenericConverter The interface .

How to use

So how do you use converters ,Spring Requires us to register all required converters to ConversionService, such Spring In the case of type conversions , Will go to ConversionService Find supported converters in , Do the necessary format conversion .

The interface that supports converter registration is ConverterRegistry

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);
}

But we're using another inheritance ConversionService and ConverterRegistry The interface of ConfigurableConversionService, Through this interface , Then you can register your custom converter .

format

The functionality provided by the converter is a one-way conversion from one type to another , And in the web In the project , Some data is often needed to do two-way conversion , The most common is the date time . Converts a formatted string in a request to a date type , The returned date type is then formatted in the specified format ,Spring The tool provided in Formatter Interface .

Formatter<T>

@FunctionalInterface
public interface Printer<T> {
String print(T object, Locale locale);
} @FunctionalInterface
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
} public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter There are two methods in the interface , One parses strings parse, One is to format a string print, You have both methods Locale Parameters of type , So you can customize it by region .

So how to use Formatter Well ? Because of annotations , A lot of demand in xml The configuration items in the ,Formatter It's also ,Spring Provides AnnotationFormatterFactory This interface .

AnnotationFormatterFactory<A extends Annotation>

public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}

getFieldTypes Methods return when these types have A I only do formatting for annotations ,getPrinter Methods and getParser Gets the corresponding objects respectively , Or we could just say Formatter Object returns .

How to use

Formatted operation , It's also basically a cast , namely String => ? and ? => String. therefore Spring Homogenize the converter with the format , In the code implementation ,Formatter It's also converted to corresponding Printer Converter and Parser converter , that ,Formatter You can also register ConversionService It's in .

You can register Formatter The interface for FormatterRegistry, The interface is inherited from ConverterRegistry, With it ConversionService The classes implemented together are FormattingConversionService.

public interface FormatterRegistry extends ConverterRegistry {
void addFormatter(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}

It is a great pity , In addition to ConversionService Of convert Use it directly ,Formatter Of print The method USES special conditions through the framework , It needs to spring Support for tags to be formatted on the page ,parse Just annotate the fields .

Write the code

Said so much , Naturally, some code is more realistic .

about Converter and ConverterFactory as well as Formatter, Use in SpringMVC There are more opportunities on parameter bindings , So directly in web In the project to write . and ConditionalGenericConverter Examples of official implementations of interfaces are already abundant , At least I didn't think of any new needs , If you want to see the code, you can go directly to the official source code ( such as ArrayToCollectionConverter), I won't write it myself .

The following code is based on SpringBoot 2.1.1, Corresponding SpringMVC by 5.1.3, Used lombok

@RestController
@RequestMapping("test")
public class TestController {
@GetMapping("/index")
public UserEntity test(UserEntity user) {
return user;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer { @Override
public void addFormatters(FormatterRegistry registry) {
// by webMVC Register converter
registry.addConverter(new String2StatusEnumConverter());
registry.addConverterFactory(new String2EnumConverterFactory());
registry.addFormatterForFieldAnnotation(new GenderFormatterFactory());
}
}
@Data
@Component
public class UserEntity { private String username;
private String password; // Annotated to use enumeration name Fields are formatted for enumeration , Can be changed to id
@GenderEnumFormat("name")
private GenderEnum gender; private StatusEnum status; }
public interface EnumInterface {
Integer getId();
}
@Getter
@AllArgsConstructor
public enum GenderEnum implements EnumInterface { MALE(0, " male "),
FEMALE(1, " Woman "),
; private Integer id;
private String name;
}
@Getter
@AllArgsConstructor
public enum StatusEnum implements EnumInterface {
ON(1, " Enable "),
OFF(0, " Discontinue use "),
; private Integer id;
private String name; }
/**
* String to StatusEnum Converter
*/
public class String2StatusEnumConverter implements Converter<String, StatusEnum> { @Override
public StatusEnum convert(String s) {
// Be careful , This is through id matching
for (StatusEnum e : StatusEnum.values()) {
if (e.getId().equals(Integer.valueOf(s))) {
return e;
}
}
return null;
}
}
/**
* String to EnumInterface Converter factory
*/
public class String2EnumConverterFactory implements ConverterFactory<String, EnumInterface> { @Override
public <T extends EnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new String2Enum<>(targetType);
} /**
* converter
*/
private class String2Enum<T extends EnumInterface> implements Converter<String, T> { private final Class<T> targetType; private String2Enum(Class<T> targetType) {
this.targetType = targetType;
} @Override
public T convert(String source) {
for (T enumConstant : targetType.getEnumConstants()) {
if (enumConstant.getId().toString().equals(source)) {
return enumConstant;
}
}
return null;
}
}
}
/**
* Will be annotated GenderEnum Converts to an enumeration by a specific field
*/
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GenderEnumFormat {
String value();
}
public class GenderFormatterFactory implements AnnotationFormatterFactory<GenderEnumFormat> {
@Override
public Set<Class<?>> getFieldTypes() {
return Collections.singleton(GenderEnum.class);
} @Override
public Printer<?> getPrinter(GenderEnumFormat annotation, Class<?> fieldType) {
return new GenderFormatter(annotation.value());
} @Override
public Parser<?> getParser(GenderEnumFormat annotation, Class<?> fieldType) {
return new GenderFormatter(annotation.value());
} final class GenderFormatter implements Formatter<GenderEnum> {
private final String fieldName;
private Method getter; private GenderFormatter(String fieldName) {
this.fieldName = fieldName;
} @Override
public GenderEnum parse(String text, Locale locale) throws ParseException {
if (getter == null) {
try {
getter = GenderEnum.class.getMethod("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
} catch (NoSuchMethodException e) {
throw new ParseException(e.getMessage(), 0);
}
}
for (GenderEnum e : GenderEnum.values()) {
try {
if (getter.invoke(e).equals(text)) {
return e;
}
} catch (IllegalAccessException | InvocationTargetException e1) {
throw new ParseException(e1.getMessage(), 0);
}
}
throw new ParseException(" Input parameter error , No such enumeration value exists :" + text, 0);
} @Override
public String print(GenderEnum object, Locale locale) {
try {
// We should also judge here getter Is it null Then choose to initialize , But because print The method doesn't work so I don't want to write it
return getter.invoke(object).toString();
} catch (IllegalAccessException | InvocationTargetException e) {
return e.getMessage();
}
}
}
}

Source notes

I've been talking about casting in Spring MVC Is useful in parameter binding , Here are some of my notes . Because of the strength of the problem in some places also some confusion , I also welcome your communication .

( Look at the source when suddenly encountered IDEA Unable to download source , The results are generally replacement maven edition , Use it if you don't want to change it maven Command to download the source :

mvn dependency:sources -DincludeArtifactIds=spring-webmvc

No parameters will be the default download all the source code )

public class InvocableHandlerMethod extends HandlerMethod {
// ...
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
if (ObjectUtils.isEmpty(this.getMethodParameters())) {
return EMPTY_ARGS;
} else {
// Gets the method parameters for the processing method
MethodParameter[] parameters = this.getMethodParameters();
Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
// initialization , And then you can call MethodParameter Object's getParameterName Method
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// If providedArgs The type containing the current parameter is assigned
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
// resolvers Contains all the parameter parsers (HandlerMethodArgumentResolver Implementation class of , Common examples are RequestParamMethodArgumentResolver,PathVariableMethodArgumentResolver etc. , This is the class that handles the annotation before the parameter , If there is a corresponding annotation, the corresponding parser is used to handle the parameter binding , If there are no annotations, there are usually ModelAttribute Use as annotations ServletModelAttributeMethodProcessor, Concrete judgment in each implementation class supportsParameter In the method )
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
} try {
// Use the parser to start parsing the parameters
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
} catch (Exception var10) {
if (this.logger.isDebugEnabled()) {
String error = var10.getMessage();
if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
this.logger.debug(formatArgumentError(parameter, error));
}
} throw var10;
}
}
} return args;
}
}
// ...
}
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
// ...
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// obtain paramter Information about ,NamedValueInfo Contains the name of the parameter 、 If required 、 The default value is , It's just that the parameter is in RequestParam Configuration in annotations
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// If parameter yes Optional type , Then a nested level is generated that points to the same argument object (nestingLevel)+1 Of MethodParameter
MethodParameter nestedParameter = parameter.nestedIfOptional();
// Resolve the configuration item and SPEL expression ( namely ${}、#{})
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// From the request (request) Gets the data for the corresponding name in , If the file is not uploaded , Equivalent to servlet Medium request.getParameter(), And if you have multiple matches name Is returned String[]
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
// The request is resolved without this parameter and with a default value defaultValue The latter value is set as a parameter
arg = resolveStringValue(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
// Parameters are required and the method type requirement is not Optional Throw exception
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// Handle null value . If the parameter type ( Or be Optional Type of package ) yes Boolean Will be converted into a false, An exception is thrown if the parameter type is primitive ( Because the base type value cannot be null)
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
// If there is a default value, the empty string will be treated as the default value
arg = resolveStringValue(namedValueInfo.defaultValue);
} if (binderFactory != null) {
// biner There is conversionService Example , and conversionService Contains all available converters .
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// Start the real casting
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
} catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause()); }
} // Hook method , The only way to override this method is temporarily PathVariableMethodArgumentResolver
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg;
}
// ...
}
class TypeConverterDelegate {
// ... /**
* Convert the value to the required type (if necessary from a String),
* for the specified property.
*
* @param propertyName name of the property
* @param oldValue the previous value, if available (may be {@code null})
* @param newValue the proposed new value
* @param requiredType the type we must convert to
* (or {@code null} if not known, for example in case of a collection element)
* @param typeDescriptor the descriptor for the target property or field
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// In the current flow propertyName、oldValue by null,newValue Is the true parameter value passed to the foreground ,requiredType Is the type required for the processing method ,typeDescriptor Encapsulates a class for the description of the required type // Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); ConversionFailedException conversionAttemptEx = null; // No custom editor but custom ConversionService specified?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
// The above conditions hold
// In the present logic sourceTypeDesc Necessary for String Of TypeDescriptor
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
// Can convert
// canConvert It's actually trying to get the right one GenericConverter, If so, you can convert
// about String -> Integer Transformation , Will be will be String The type release is [String,Serializable,Comparable,CharSequence,Object] The type of layer ,Integer Also split into its own type layer , Each type is then traversed to determine exactly whether a converter exists that can be converted
try {
// The custom converter is eventually invoked
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException ex) {
// fallback to default conversion logic below
// switch views , Temporary abnormal , The default transformation logic will be executed
conversionAttemptEx = ex;
}
}
}
// because spring Comes with many common types of converters , Most of this can be done with the converter above .
// If the program runs up to this point without ending it probably means that the type is a custom type that does not define the converter or that the parameter format is really incorrect Object convertedValue = newValue; // Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
// The final condition is When newValue No requiredType Example
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
// isAssignableFrom Used to determine Collection Is it requiredType Parent class or interface , Or whether they are the same type or interface
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
// amount to convertedValue.split(",")
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
// Use default editor convert , But by default editor The transformation may not be as expected .( such as "1,2,3,4" -> ArrayList<String>{"1,2,3,4"}, The result is one element list)
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
} boolean standardConversion = false; // And that will add up to requiredType To make the corresponding conversion
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) {
if (Object.class == requiredType) {
// requiredType yes Object
return (T) convertedValue;
} else if (requiredType.isArray()) {
// requiredType It's an array
// Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
} else if (convertedValue instanceof Collection) {
// take convertedValue Convert to set , Each element is called internally convertIfNecessary( Is this method )
// Convert elements to target type, if determined.
convertedValue = convertToTypedCollection(
(Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
} else if (convertedValue instanceof Map) {
// take convertedValue Convert to Map
// Convert keys and values to respective target type, if determined.
convertedValue = convertToTypedMap(
(Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
// We can stringify any primitive value...
return (T) convertedValue.toString();
} else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
try {
Constructor<T> strCtor = requiredType.getConstructor(String.class);
return BeanUtils.instantiateClass(strCtor, convertedValue);
} catch (NoSuchMethodException ex) {
// proceed with field lookup
if (logger.isTraceEnabled()) {
logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
}
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
}
}
}
String trimmedValue = ((String) convertedValue).trim();
if (requiredType.isEnum() && trimmedValue.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
// Try to convert to an enumeration
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion = true;
} else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
convertedValue = NumberUtils.convertNumberToTargetClass(
(Number) convertedValue, (Class<Number>) requiredType);
standardConversion = true;
}
} else {
// convertedValue == null
if (requiredType == Optional.class) {
convertedValue = Optional.empty();
}
} if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (conversionAttemptEx != null) {
// Original exception from former ConversionService call above...
throw conversionAttemptEx;
} else if (conversionService != null && typeDescriptor != null) {
// ConversionService not tried before, probably custom editor found
// but editor couldn't produce the required type...
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
} // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
StringBuilder msg = new StringBuilder();
msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
if (propertyName != null) {
msg.append(" for property '").append(propertyName).append("'");
}
if (editor != null) {
msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
"] returned inappropriate value of type '").append(
ClassUtils.getDescriptiveType(convertedValue)).append("'");
throw new IllegalArgumentException(msg.toString());
} else {
msg.append(": no matching editors or conversion strategy found");
throw new IllegalStateException(msg.toString());
}
}
} if (conversionAttemptEx != null) {
if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
throw conversionAttemptEx;
}
logger.debug("Original ConversionService attempt failed - ignored since " +
"PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
} return (T) convertedValue;
}
// ...
}

Last

Look at the source code although it takes a lot of time , But there is a lot to learn , And you can discover things you didn't know before ( such as RequestParam Annotated name and defaultName Parameters can be nested to reference the contents of a configuration file , Can also write SPEL expression ), But there are still some areas that are not very clear .

Although now the project is used directly JSON Do front and back interactions , Most of the casting is done JSON Serialization framework , But parameter bindings are worth a look here , When you need it, you can just take it out and use it .

Spring Type conversion (Converter) More articles about

  1. turn :SpringMVC Type conversion of Converter(GenericConverter)

    turn : http://blog.csdn.net/fsp88927/article/details/37692215 SpringMVC Type conversion of Converter 1.1 Catalog 1.1 Catalog 1. ...

  2. SpringMVC Type conversion of Converter Detailed explanation reprint

    SpringMVC Type conversion of Converter Detailed explanation This article is reprinted. http://www.tuicool.com/articles/uUjaum 1.1      Catalog 1.1       Catalog 1.2     ...

  3. SpringMVC Type conversion of Converter Source code analysis

    SpringMVC Type conversion of Converter Source code analysis Recent research SpringMVC Type converter for , In the past, we needed  SpringMVC  It's used for automatic type conversion PropertyEdito ...

  4. Spring Type Conversion(Spring Type conversion source code exploration )

    1: summary The type conversion system is responsible for Spring Object type conversion and formatting in the framework . ConversionService Default implementation UML The figure is shown below : GenericConversionService( Generic type conversion services ...

  5. Spring Type Conversion(Spring Type conversion )

    Spring Type Conversion(Spring Type conversion ) 1: summary : Spring3 Introduced core.convert package , Provides a general type conversion system , Defines the implementation of type conversion and runtime execution type SPI. ...

  6. 1. reveal Spring Type conversion - The cornerstone of frame design

    feel no shame before God , I'm worthy of it , Be worthy of one's heart . Official account [BAT The Utopia of ], Yes Spring Technology stack .MyBatis.JVM. Middleware and other small and beautiful original columns for free learning . Share . grow up , Refuse to stop . This article has been https://ww ...

  7. spring Automatic type conversion ========Converter and PropertyEditor

    Spring There are two automatic type converters , One is Converter, One is propertyEditor. The difference between the two :Converter It's type to type ,Editor: from string Type conversion to other types . From ...

  8. 【Spring Learning notes -MVC-8】SpringMVC Type conversion of Converter

    author :ssslinppp       1. Abstract stay spring It defines 3 Type conversion interface in , Respectively : Converter Interface              : Easy to use , The most inflexible : ConverterFa ...

  9. SpringMVC Type conversion of Converter

    ( Reprint :http://blog.csdn.net/renhui999/article/details/9837897) 1.1      Catalog 1.1       Catalog 1.2       Preface 1.3   ...

Random recommendation

  1. Use text Storage hash Data of type Use text filed to store the hash map

    stay component For the inside and the outside text Field storage of type hash data (1) New field , This is a migration The content of class AddHintsToComponents < ActiveRecord: ...

  2. kuangbin_MST B (POJ 1287)

    Naked template questions Because the adjacency matrix is used directly, the repeated edges of the previous input are covered with the minimum value #include <cstdio> #include <cstring> #include <queue ...

  3. 14. Joseph Ring problem [JosephusProblem]

    [ subject ] n A digital (0,1,…,n-1) Form a circle , From numbers 0 Start , Every time you delete the second... From this circle m A digital ( The first is the current number itself , The second is the next number of the current number ). When a number is deleted , Continue to delete from the next deleted number ...

  4. Simulated annealing algorithm -[HDU1109]

    The principle of simulated annealing algorithm is derived from the principle of solid annealing , Heat the solid to a sufficiently high temperature , And let it cool down , On heating , The particles in the solid become disordered as the temperature rises , Internal energy increases , As it cools slowly, the particles become more ordered , Equilibrium at every temperature , Finally at room temperature ...

  5. MongoDB Rights management Operation of user name and password

    Just after installation MongoDB By default, there is one admin database , and admin.system.users User information with more privileges than those set in other databases will be saved .  When admin.system.users in ...

  6. hdu 4857 escape A topological sort +PQ, Delamination analysis

    pid=4857">hdu4857  escape The topic is topological ordering , But not in dictionary order , It's about putting smaller numbers at the top . The wrong way to start : Give each point a priority ( The smallest point that can be reached ) ...

  7. Eclipse Of Spring IDE Installation and use of plug-ins

    Spring IDE yes Spring Recommended by the official website Eclipse plug-in unit , Available in R & D Spring When the Bean Define the file for validation and visually view each Bean The dependence between . install Use Eclipse M ...

  8. HDU4310HERO The problem of greed

    Problem description When playing DotA with god-like rivals and pig-like team members, you have to face an embarrass ...

  9. Spring boot Integrate mybaits plus( Code generator )

    Source code address :https://github.com/YANGKANG01/Spring-Boot-Demo Code generation operations stay pom.xml The following package is introduced into the file : <!-- mybatisplus And spr ...

  10. Windows 7 x64 install Oracle 11g Express

    1. download     Download the file here : Oracle Database Express Edition 11g Release 2 for Windows x32    Oracle Does not provide window ...