SpringMVC视图解析器InternalResourceViewResolver问题分析

今天在搭建SpringMVC开发框架的时候,出现freemarker的视图没有找到,报404错误。我的配置代码如下:

<!--freemarker -->
<mvc:view-controller path="/" view-name="homepage/index”/>

<!-- jsp -->
<mvc:view-controller path="/login" view-name="login”/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="order" value="2"/>
</bean>

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="prefix" value="" />
    <property name="suffix" value=".ftl" />
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="order" value="3"/>
</bean>

我配置了两个视图解析器,所以视图解析是链式的,如果一个视图解析器没有找到对应的view-name,则开始找第二的解析器。InternalResourceViewResolver的property中的order值是2,FreeMarkerViewResolver的property的order为3,我们知道order,从小到大,越小的优先级越高,那么InternalResourceViewResolver一定是先运行的。

InternalResourceViewResolver的父类UrlBasedViewResolver中有一个方法loadView用于创建加载视图,源码如下:

/**
 * Delegates to {@code buildView} for creating a new instance of the
 * specified view class, and applies the following Spring lifecycle methods
 * (as supported by the generic Spring bean factory):
 * <ul>
 * <li>ApplicationContextAware's {@code setApplicationContext}
 * <li>InitializingBean's {@code afterPropertiesSet}
 * </ul>
 *
 * @param viewName the name of the view to retrieve
 * @return the View instance
 * @throws Exception if the view couldn't be resolved
 * @see #buildView(String)
 * @see org.springframework.context.ApplicationContextAware#setApplicationContext
 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
 */
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}

最后一行用到了view的checkResource方法。
我们看到InternalResourceViewResolver对应的view是InternalResourceView,它的子类只有JstlView,下面是view的类图关系。

查看InternalResourceView的父类AbstractUrlBasedView,可以找到它里面的checkResource方法,源码如下:

/**
 * Check whether the underlying resource that the configured URL points to
 * actually exists.
 *
 * @param locale the desired Locale that we're looking for
 * @return {@code true} if the resource exists (or is assumed to exist);
 * {@code false} if we know that it does not exist
 * @throws Exception if the resource exists but is invalid (e.g. could not be parsed)
 */
public boolean checkResource(Locale locale) throws Exception {
    return true;
}

可以看到这个方法直接返回true,根本就没有check。
至此我们就找到问题的原因了,InternalResourceView是不会check资源文件是否存在,当InternalResourceViewResolver先运行的时候,遇到其他的view-name如本例的freemarker的文件根本不会做check,导致最终出现404的情况。

如何解决这个问题呢?
第一种方法:把order的值修改下,把InternalResourceViewResolver的order改成最大的,即最后解析让其他的会check文件是否存在的解析器先运行。
第二种方法:自定义一个view类继承JstlView,自己写一个checkResource将父类的的checkResource override掉。代码如下:

package com.tonitech.ancestor.common;

import org.springframework.web.servlet.view.JstlView;

import java.io.File;
import java.util.Locale;

/**
* 解决多个ViewResolver时jsp获取不到时,跳转到下一个ViewResolver
*/
public class DefaultJstlView extends JstlView {

    @Override
    public boolean checkResource(Locale locale) throws Exception {
        File file = new File(this.getServletContext().getRealPath("/") + getUrl());
        return file.exists();//判断该jsp页面是否存在
    }
}

然后在InternalResourceViewResolver的bean配置里加一行:

<property name="viewClass" value="com.tonitech.ancestor.common.DefaultJstlView"/>

通过这两种方法都可以解决问题。