一、问题描述
今天在Java群里看到“白日梦想家” 的一个提问,很有意思:
为什么 String类型的列表 通过spring的属性注入 可以注入Integer类型的元素呢?
二、问题分析过程
经过一番调试发现了关键所在(Spring 5.0.10.Release版本代码):
AbstractAutowireCapableBeanFactory类中的applyPropertyValues函数将属性值PropertyValues解析到beanName对应的Bean的属性上。
/**
* Apply the given property values, resolving any runtime references
* to other beans in this bean factory. Must use deep copy, so we
* don't permanently modify this property.
* @param beanName the bean name passed for better exception information
* @param mbd the merged bean definition
* @param bw the BeanWrapper wrapping the target object
* @param pvs the new property values
*/
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
// ① 如果PropertyValues为空,直接返回
if (pvs.isEmpty()) {
return;
}
// ②判断安全管理器
if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
}
MutablePropertyValues mpvs = null;
List<PropertyValue> original;
// ③ 获取bean的属性集合
// 如果pvs是MutablePropertyValues的实例,MutablePropertyValues是PropertyValues的默认实现
if (pvs instanceof MutablePropertyValues) {
// 将pvs转换为MutablePropertyValues对象,并判断mpvs是否已经经过转换
mpvs = (MutablePropertyValues) pvs;
if (mpvs.isConverted()) {
// Shortcut: use the pre-converted values as-is.
// 如果pvs已经转换过,则直接设置属性值无需再次转换
try {
bw.setPropertyValues(mpvs);
return;
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
// 否则获取原始PropertyValue集合
original = mpvs.getPropertyValueList();
}
else {
original = Arrays.asList(pvs.getPropertyValues());
}
// ④ 获取类型转换器
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
// Create a deep copy, resolving any references for values.
// ⑤ 通过深度拷贝,解析值引用
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
// 循环解析PropertyValues
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
}
else {
//获取属性值
String propertyName = pv.getName();
Object originalValue = pv.getValue();
// 解析原始属性值
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
// isWritableProperty 判断属性是否可写,如果属性不存在返回false
// isNestedOrIndexedProperty 判断是否索引属性或者嵌套属性
boolean convertible = bw.isWritableProperty(propertyName) &&
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
// Possibly store converted value in merged bean definition,in order to avoid re-conversion for every created bean instance.
// ⑥缓存已经转换过的值,避免再次转换
if (resolvedValue == originalValue) {
if (convertible) {
pv.setConvertedValue(convertedValue);
}
deepCopy.add(pv);
}
else if (convertible && originalValue instanceof TypedStringValue &&
!((TypedStringValue) originalValue).isDynamic() &&
!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
pv.setConvertedValue(convertedValue);
deepCopy.add(pv);
}
else {
resolveNecessary = true;
deepCopy.add(new PropertyValue(pv, convertedValue));
}
}
}
if (mpvs != null && !resolveNecessary) {
mpvs.setConverted();
}
// Set our (possibly massaged) deep copy.
// ⑦设置属性值.
try {
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
最关键的在这行代码(它对List中元素的类型进行类型转换):
boolean convertible = bw.isWritableProperty(propertyName) &&
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
具体转换在convertForProperty的函数调用:
TypeConverterDelegate的convertIfNecessary
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 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) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
if (requiredType != null) {
// Try to apply some standard type conversion rules if appropriate.
if (convertedValue != null) {
if (Object.class == requiredType) {
return (T) convertedValue;
}
else if (requiredType.isArray()) {
// 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) {
// Convert elements to target type, if determined.
convertedValue = convertToTypedCollection(
(Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
else if (convertedValue instanceof 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() && "".equals(trimmedValue)) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
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;
}
的213行处实现转换,转换前(注意观察convertedValue,集合的元素类型),转换前为整型:
转换后为字符串类型:
其中TypeConverterDelegate的convertToTypedCollection代码如下:
private Collection<?> convertToTypedCollection(Collection<?> original, @Nullable String propertyName,
Class<?> requiredType, @Nullable TypeDescriptor typeDescriptor) {
if (!Collection.class.isAssignableFrom(requiredType)) {
return original;
}
boolean approximable = CollectionFactory.isApproximableCollectionType(requiredType);
if (!approximable && !canCreateCopy(requiredType)) {
if (logger.isDebugEnabled()) {
logger.debug("Custom Collection type [" + original.getClass().getName() +
"] does not allow for creating a copy - injecting original Collection as-is");
}
return original;
}
boolean originalAllowed = requiredType.isInstance(original);
TypeDescriptor elementType = (typeDescriptor != null ? typeDescriptor.getElementTypeDescriptor() : null);
if (elementType == null && originalAllowed &&
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
return original;
}
Iterator<?> it;
try {
it = original.iterator();
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot access Collection of type [" + original.getClass().getName() +
"] - injecting original Collection as-is: " + ex);
}
return original;
}
Collection<Object> convertedCopy;
try {
if (approximable) {
convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
}
else {
convertedCopy = (Collection<Object>)
ReflectionUtils.accessibleConstructor(requiredType).newInstance();
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot create copy of Collection type [" + original.getClass().getName() +
"] - injecting original Collection as-is: " + ex);
}
return original;
}
int i = 0;
for (; it.hasNext(); i++) {
Object element = it.next();
String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
// 对集合中的每个元素进行转换(如果需要)
Object convertedElement = convertIfNecessary(indexedPropertyName, null, element,
(elementType != null ? elementType.getType() : null) , elementType);
try {
convertedCopy.add(convertedElement);
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Collection type [" + original.getClass().getName() +
"] seems to be read-only - injecting original Collection as-is: " + ex);
}
return original;
}
originalAllowed = originalAllowed && (element == convertedElement);
}
return (originalAllowed ? original : convertedCopy);
}
其中此处为集合中每个元素进行转换(再次调用convertIfNecessary函数)
经过上面分析,我们知道List<Integer>可以顺利注入到List<String>中的原因了,因为中间经历了属性转换。
另外有一个童鞋提出可以将配置文件中<value>节点的值改为字符串如下图所示:
然后注入List<Integer>的Bean属性中。
我们发现会报错:
那么说明 字符串无法通过上面的转换函数转成整数吗?
另外我们注意到代码企图利用Integer.valueOf函数将字符串转成整型,按道理说应该是可以的。
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
但是我们根据报错如果我们细心可以发现这里并不是字符串1 ("1")而是字符串(""1""), 红色部分表示字符串的实际内容。
那我们再次修改配置文件
我们启动项目发现一切正常。
我们打条件断点回到之前的位置查看
走过如上代码后字符串类型的集合转成了整数集合
因此如果是可以转换的类型Spring会对属性进行转换,如果是无法转换将会报错。特别要注意这里的
<bean id="propertiesBean" class="com.chujianyun.spring.property.PropertiesBean">
<property name="data">
<list>
<value type="java.lang.String">"1"</value>
<value type="java.lang.String">"2"</value>
</list>
</property>
</bean>
的含义并不是字符串1和字符串2 而是字符串"1"(三个字符组成)和字符串"2"。
为了证明我们的想法,重新打断点:
发现的确如此,将字符串 "1"(三个字符)通过Integer.valueOf转成整型显然会报错。
另外我们如果将属性修改如下:
显然这里的字符串s无法转换为整型(字符串1 可以),会报错。
另外我们根据报错可以了解Spring创建Bean的大致步骤(每一部分调用顺序都是从下往上)
三、总结:
遇到问题可以浅尝辄止,也可以借此机会深入了解问题的本源,对熟悉源码加深理解有很大帮助。
建议大家多拉取核心技术栈的源码,遇到问题多分析调试,理解会更好一些。
遇到问题是研究源码的最好的时机,每一次研究对技术的进步都有很大帮助。
另外下载源码后想了解某个类的某个方法的使用方式,可以右键find usages找到对应的单元测试后打断点进行调试,学习的效果非常好。
附:SpringBoot源码环境搭建的正确姿势
https://blog.csdn.net/w605283073/article/details/85106902