<mvc:resources />标签新老版本解析不同,是bug还是?
来源:互联网 发布:医疗器械注册 知乎 编辑:程序博客网 时间:2024/06/06 12:37
先来说说这个坑爹的问题,其实本来我是没注意到的,因为程序跑起来一切都正常。但是在tomcat启动时飞速打印log时,在中间“隐藏”了一个错误:
2015-02-15 16:03:22 [ catalina-exec-4:2202 ] - [ DEBUG ] [org.springframework.beans.TypeConverterDelegate] Original ConversionService attempt failed - ignored since PropertyEditor based conversion eventually succeededorg.springframework.core.convert.ConversionFailedException: Failed to convert from type java.util.ArrayList<?> to type java.util.List<org.springframework.core.io.Resource> for value '[classpath:/plugin/assets/]'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type java.lang.String to type org.springframework.core.io.Resource at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:168) at ........此处省略Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type ... 61 more
看这个错误应该是在类型转换的时候报的错。也就是将
<mvc:resources location="" mapping="" />
中的location
转换成List<Resource>
的时候报错的。Spring3中使用的是ConversionService
来对外提供转换接口,这里我确实注册了一个ConversionService
,但那也只是在默认之上添加了一个自己实现的DateFormatter
,应该不会有影响的。
那到底是什么原因呢?我觉得像我这样的人,星期六在家完全不适合碰到稍微需要分析一下的问题。因为我容易逃避啊,觉得烦就跑去打Dota了,图个脑袋轻松。还好今天上班,可以来好好地分析一下这个问题。
其实我一开始并没有想到是<mvc:resources />
标签解析的问题,而是按照Dispatcher启动的顺序来做调试的。关于Dispatcher的启动过程,我打算在下一篇文章中来记录。通过上面繁琐的跟踪,最终定位到org.springframework.beans.TypeConverterDelegate
类中的 convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType, TypeDescriptor typeDescriptor)
方法:
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException { Object convertedValue = newValue; // Custom editor for this type? PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); ConversionFailedException firstAttemptEx = null; // No custom editor but custom ConversionService specified? ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); TypeDescriptor targetTypeDesc = typeDescriptor; if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) { try { return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc); } catch (ConversionFailedException ex) { // fallback to default conversion logic below firstAttemptEx = ex; } } } // Value not of required type? if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) { TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor(); if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } } if (editor == null) { editor = findDefaultEditor(requiredType); } convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); } boolean standardConversion = false; if (requiredType != null) { if (convertedValue != null) { // 省略部分代码,都是根据条件来调用不同的转换方法 else if (convertedValue instanceof Collection) { // Convert elements to target type, if determined. convertedValue = convertToTypedCollection( (Collection) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } // 省略部分代码,都是根据条件来调用不同的转换方法 } // 省略部分代码 } if (firstAttemptEx != null) { if (editor == null && !standardConversion && requiredType != null && !Object.class.equals(requiredType)) { throw firstAttemptEx; } logger.debug("Original ConversionService attempt failed - ignored since " + "PropertyEditor based conversion eventually succeeded", firstAttemptEx); } return (T) convertedValue; }
断点进来发现老项目和新项目某些变量的类型居然不一样:
在老项目中的newValue
是String[]
,而在新项目中的newValue
则是一个ArrayList
,一样的代码怎么就产生不同的结果了呢?那我们继续跟踪,看看这个newValue
究竟是怎么来的。发现newValue
是在AbstractAutowireCapableBeanFactory
类的applyPropertyValues(...)
方法里得到的:
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { // 省略部分代码 if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; if (mpvs.isConverted()) { // Shortcut: use the pre-converted values as-is. try { bw.setPropertyValues(mpvs); return; } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } 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<PropertyValue>(original.size()); boolean resolveNecessary = false; 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; boolean convertible = bw.isWritableProperty(propertyName) && !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); if (convertible) { // 这里的resolvedValue就是后面的newValue 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)); } } } // 省略部分代码 }
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { if (pvs == null || pvs.isEmpty()) { return; } MutablePropertyValues mpvs = null; List<PropertyValue> original; if (System.getSecurityManager() != null) { if (bw instanceof BeanWrapperImpl) { ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); } } if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; if (mpvs.isConverted()) { // Shortcut: use the pre-converted values as-is. try { bw.setPropertyValues(mpvs); return; } catch (BeansException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } 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<PropertyValue>(original.size()); boolean resolveNecessary = false; 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; 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); } }
下图是调试的信息:
诶,这里新项目的originalValue
为什么是ManagedList
类型的,这也是我第一次看到它。而它是通过PropertyValue.getValue()
得到的,PropertyValue不就是我们设的location属性的值吗?这里怎么被封装成ManagedList
了?总算想到可能是<mvc:resources />
惹的祸了,于是看下新项目中解析该标签的关键源码:
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) { ManagedList<String> locations = new ManagedList<String>(); locations.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(locationAttr))); RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class); resourceHandlerDef.setSource(source); resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); resourceHandlerDef.getPropertyValues().add("locations", locations); return beanName; }
终于看到这个”熟悉”的ManagedList
了,然后又去检查了下老项目中该解析类的关键源码:
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) { RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class); resourceHandlerDef.setSource(source); resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); resourceHandlerDef.getPropertyValues().add("locations", StringUtils.commaDelimitedListToStringArray(locationAttr)); return beanName; }
然后。。。然后。。。就没有然后了,已经吐血不省人事。回过头来,既然知道原因了,那么赶紧解决了它!不使用<mvc:resources>
标签就行,还记得之前我写过一篇关于处理静态资源的文章吗?传送门
该标签的本质我们都知道了,直接手动注册就好了~至此,问题终于解决!
不过有一点我不知道的是,为什么Spring项目组要将ResourcesBeanDefinitionParser
中解析location
的代码改掉?希望有知道的朋友可以跟我讲讲,多谢!
最后有一点体会:越是让人心烦的事情,解决了之后越是让人舒心。嘿嘿~
- <mvc:resources />标签新老版本解析不同,是bug还是?
- <mvc:resources> 标签解释
- <mvc:resources> 标签解释
- <mvc:resources> 标签解释
- <mvc:resources> 标签
- 是Bug,还是Bug?
- <mvc:resources> 标签的使用
- 老版本IE中的BUG
- 新老版本的更换
- SharedPreferences新老版本兼容
- 关于spring <mvc:resources> 标签的使用
- SpringMVC <mvc:resources mapping=“”/> 标签
- 关于MVC到底是设计模式还是框架又或者是架构模式的新看法
- Insight mvc:resources cache-period 解析
- [bug]SVN版本太老无法更新
- MVC静态资源映射 <mvc:resources> 标签的使用
- spring mvc中<mvc:resources> 标签的使用
- Spring MVC 静态资源访问 mvc:resources 标签
- unity3d meshBaker教程(一) 基本的使用
- 解读条件变量
- Java jni调用c函数实例
- sgu124
- 南邮NOJ1009 2的n次方
- <mvc:resources />标签新老版本解析不同,是bug还是?
- Hibernate中对象的三种状态
- 学习笔记:WPF之Binding深入探讨
- python 中的OptionParser的使用例子
- Han Solo and Lazer Gun (Codeforces Round #291 (Div. 2)B)
- 如何为 SpringMVC 编写单元测试:从配置开始
- 关于网络通信
- 第一次接触 selenium
- 关于python的_init_()方法的一点解释