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

断点进来发现老项目和新项目某些变量的类型居然不一样:
这里写图片描述
在老项目中的newValueString[],而在新项目中的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的代码改掉?希望有知道的朋友可以跟我讲讲,多谢!

最后有一点体会:越是让人心烦的事情,解决了之后越是让人舒心。嘿嘿~

4 0
原创粉丝点击