DependencyDescriptor supports TypeDescriptor resolution for fields

This allows for proper nested type conversion in @Value Optional fields analogous to method parameters, through a new TypeDescriptor-based method in the TypeConverter SPI. As an additional and less involved measure that is worth backporting, DefaultListableBeanFactory defensively checks for pre-converted Optional wrappers.

Issue: SPR-17607
This commit is contained in:
Juergen Hoeller
2019-01-08 17:11:27 +01:00
parent a82f049083
commit 9cb5369cb9
8 changed files with 117 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.beans;
import java.lang.reflect.Field;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
/**
@@ -93,4 +94,27 @@ public interface TypeConverter {
<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)
throws TypeMismatchException;
/**
* Convert the value to the required type (if necessary from a String).
* <p>Conversions from String to any type will typically use the {@code setAsText}
* method of the PropertyEditor class, or a Spring Converter in a ConversionService.
* @param value the value to convert
* @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 type descriptor to use (may be {@code null}))
* @return the new value, possibly the result of type conversion
* @throws TypeMismatchException if type conversion failed
* @since 5.1.4
* @see java.beans.PropertyEditor#setAsText(String)
* @see java.beans.PropertyEditor#getValue()
* @see org.springframework.core.convert.ConversionService
* @see org.springframework.core.convert.converter.Converter
*/
@Nullable
default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
throw new UnsupportedOperationException("TypeDescriptor resolution not supported");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
@@ -82,42 +81,6 @@ class TypeConverterDelegate {
}
/**
* Convert the value to the specified required type.
* @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 methodParam the method parameter that is the target of the conversion
* (may be {@code null})
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
@Nullable
public <T> T convertIfNecessary(@Nullable Object newValue, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws IllegalArgumentException {
return convertIfNecessary(null, null, newValue, requiredType,
(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
}
/**
* Convert the value to the specified required type.
* @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 field the reflective field that is the target of the conversion
* (may be {@code null})
* @return the new value, possibly the result of type conversion
* @throws IllegalArgumentException if type conversion failed
*/
@Nullable
public <T> T convertIfNecessary(@Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable Field field)
throws IllegalArgumentException {
return convertIfNecessary(null, null, newValue, requiredType,
(field != null ? new TypeDescriptor(field) : TypeDescriptor.valueOf(requiredType)));
}
/**
* Convert the value to the required type for the specified property.
* @param propertyName name of the property

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.lang.reflect.Field;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -41,15 +42,16 @@ public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {
return doConvert(value, requiredType, null, null);
return convertIfNecessary(value, requiredType, TypeDescriptor.valueOf(requiredType));
}
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam)
throws TypeMismatchException {
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
return doConvert(value, requiredType, methodParam, null);
return convertIfNecessary(value, requiredType,
(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
}
@Override
@@ -57,21 +59,18 @@ public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)
throws TypeMismatchException {
return doConvert(value, requiredType, null, field);
return convertIfNecessary(value, requiredType,
(field != null ? new TypeDescriptor(field) : TypeDescriptor.valueOf(requiredType)));
}
@Nullable
private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException {
@Override
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
if (field != null) {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
}
else {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
}
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@@ -77,6 +78,9 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
@Nullable
private transient volatile ResolvableType resolvableType;
@Nullable
private transient volatile TypeDescriptor typeDescriptor;
/**
* Create a new descriptor for a method or constructor parameter.
@@ -301,7 +305,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
}
/**
* Build a ResolvableType object for the wrapped parameter/field.
* Build a {@link ResolvableType} object for the wrapped parameter/field.
* @since 4.0
*/
public ResolvableType getResolvableType() {
@@ -315,6 +319,21 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
return resolvableType;
}
/**
* Build a {@link TypeDescriptor} object for the wrapped parameter/field.
* @since 5.1.4
*/
public TypeDescriptor getTypeDescriptor() {
TypeDescriptor typeDescriptor = this.typeDescriptor;
if (typeDescriptor == null) {
typeDescriptor = (this.field != null ?
new TypeDescriptor(getResolvableType(), getDependencyType(), getAnnotations()) :
new TypeDescriptor(obtainMethodParameter()));
this.typeDescriptor = typeDescriptor;
}
return typeDescriptor;
}
/**
* Return whether a fallback match is allowed.
* <p>This is {@code false} by default but may be overridden to return {@code true} in order

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1183,13 +1183,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
@@ -1687,7 +1694,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
super.resolveCandidate(beanName, requiredType, beanFactory));
}
};
return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null));
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
return (result instanceof Optional ? (Optional<?>) result : Optional.ofNullable(result));
}
@@ -1915,8 +1923,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private Stream<Object> resolveStream(boolean ordered) {
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered);
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
Assert.state(result instanceof Stream, "Stream expected");
return (Stream<Object>) result;
return (result instanceof Stream ? (Stream<Object>) result : Stream.of(result));
}
}