这是因为根据JSF规范,
ViewExpiredException
被包装在
ServletException
中。以下是
JSF 1.2规范第10.2.6.2章的摘录:
10.2.6.2 FacesServlet
调用已保存的Lifecycle
实例的execute()
方法,并将此请求的FacesContext
实例作为参数传递。如果execute()
方法引发FacesException
,则将其作为ServletException
重新抛出,并以FacesException
作为根本原因。
错误页面的分配方式由Servlet API规范指定。以下是
Servlet API规范2.5第9.9.2章的摘录:
SRV.9.9.2错误页面
如果没有包含exception-type
的error-page
声明符合类层次结构匹配,并且抛出的异常是ServletException
或其子类,则容器提取被包装的异常,如ServletException.getRootCause
方法所定义。然后进行第二次匹配,再次尝试与错误页面声明进行匹配,但使用被包装的异常。
在类层次结构中,ServletException
已经匹配了Throwable
,因此其根本原因不会在第二次匹配中被提取。
要证明这个特定的行为,请将javax.faces.application.ViewExpiredException
替换为javax.servlet.ServletException
作为<exception-type>
,然后重试。您将看到预期的错误页面被显示。
要解决这个问题,只需删除java.lang.Throwable
或java.lang.Exception
上的错误页面即可。如果没有一个特定于异常的错误页面匹配,那么它仍然会回退到500
的错误代码。所以,你所需要的就是这个:
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/jsps/utility/sessionExpired.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/jsps/utility/technicalError.jsp</location>
</error-page>
更新:根据OP的(已删除)评论:要可靠地测试这个,您不能在bean构造函数或方法中执行throw new ViewExpiredException()
。这将进而被包装在某些EL异常中。您最终可以在Filter
中添加一个打印rootCause
的调试行,以便自己查看。
如果您正在使用Eclipse/Tomcat,则快速测试ViewExpiredException
的方法如下:
- 创建一个带有简单命令按钮的JSF页面,部署并运行它,并在Web浏览器中打开它。
- 返回到Eclipse,在Tomcat服务器上右键单击并选择Clean Tomcat Work Directory。这将重新启动Tomcat并清除所有序列化会话(重要!仅重新启动Tomcat是不够的)。
- 返回到Web浏览器,并在不重新加载页面的情况下按下命令按钮。
java.lang.Throwable
/java.lang.Exception
并将500
保留为其他异常的通用回退。另请参见答案。 - BalusC<error-code>500
。 - BalusC