JSF在托管Bean构造函数发送404错误代码时调用方法

5
在JSF管理的bean构造函数中,我使用请求参数从数据库中加载实体。有时,该实体并不存在于数据库中,我想显示其他带有404消息的JSF(.xhtml)页面。
以下是受管理bean的示例:
@ManagedBean(name = "someBean")
@RequestScoped
public class SomeBean implements Serializable {

    private static final long serialVersionUID = 1L;

    private SomeData someData;

    public SomeBean() throws IOException {
        someData = ... loads from database using JPA features
        if(someData == null){
              HttpServletResponse response = (HttpServletResponse) FacesContext
                    .getCurrentInstance().getExternalContext().getResponse();
              response.sendError(404);
        }
    }

    public SomeData getSomeData(){
        return someData;
    }
}

我配置了类似这样的web.xml文件:

<error-page>
   <error-code>404</error-code>
   <location>/404.xhtml</location>
</error-page>

我有一个JSF页面来处理由托管bean加载的实体。当实体存在时,我将在页面中使用它。就像这样:

<h1>#{someBean.someEntity.name}</h1>
<h2>#{someBean.someEntity.description}</h2>
<ui:repeat value="#{someBean.someEntity.books}" var="book">
// ..........
</ui:repeat>

上述页面在数据成功加载时才能正常工作。
问题:
当实体不存在且我发送404错误代码时,JSF仍会处理第一页表达式语言中定义的方法。这种行为会导致托管bean抛出NullPointerException和HTTP 500 ERROR CODE。我的404错误页面没有被调用。我不知道为什么。
即使数据库中找到实体,我也尝试发送404错误,404错误页面也可以正常工作。
有人能向我解释一下JSF的这种行为吗?或者提供一些不改变URL的方式来显示404错误页面?

什么服务器?FacesServlet的url-mapping是什么?至少在Wildfly上,将其映射到*.xhtml时似乎可以工作。 - Jaqen H'ghar
我使用Tomcat 7,带有Servlet 3.0 API和JSF 2.2。这个问题也会在其他JSF 2版本中出现。 - Welyab Paula
我使用Tomcat 7,带有Servlet 3.0 API和JSF 2.2。该问题也发生在其他jsf 2版本中。我使用手动配置的WELD CDI实现,但是在没有WELD CDI的Tomcat中也会出现问题。我尝试使用FacesContext.getCurrentInstance().responseComplete();,但仍然无效。我认为这种行为是符合JSF生命周期的,但我无法理解它。 - Welyab Paula
我使用通常的FacesServlet映射(/faces/*),并使用PrettyFaces将URL映射到.xhtml文件;除了报告的问题外,一切都正常。<br/><br/>即使没有PrettyFaces,这个问题也会发生。 - Welyab Paula
1个回答

5

在渲染视图时,您基本上试图执行前端控制器逻辑。您应该在渲染视图之前执行此操作。因为一旦开始渲染视图,改变视图到不同的目的地(例如,在您的情况下是错误页面)已经太迟了。您不能从客户端返回已发送的响应。

在JSF 2.2中,您可以使用<f:viewAction>进行此操作。

<f:metadata>
    <f:viewAction action="#{bean.init}" />
</f:metadata>

public void init() {
    // ...

    if (someCondition) {
        context.getExternalContext().responseSendError(404, "some message");
        context.responseComplete();
    }
}

请注意,每当您需要将javax.servlet.*类导入到JSF支持的bean中时,请务必停下来查看功能是否已经在ExternalContext中可用,或者仔细思考您是否正在正确地进行操作,例如,可能需要一个servlet过滤器;还要注意,您需要明确告诉JSF您已完成响应,否则它仍将尝试呈现视图。
在JSF 2.0 / 2.1中,您可以使用<f:event type="preRenderView">。另请参见其他内容
如果您实际上正在验证HTTP请求参数,并且您也恰好使用OmniFaces,则可以考虑使用具有真正JSF验证器的<f:viewParam>,并使用OmniFaces <o:viewParamValidationFailed>控制sendError

我收到了 HTTP状态500 - 在响应已提交后无法创建会话 的错误,这是由于在支持bean中的init方法中调用了简单的 f:viewAction,而该init方法只是调用了 ...getExternalContext().responseSendError(404, null); - Welyab Paula
我从Mojarra查看jsf的源代码,发现responseSendError只是调用了HttpServletResponse.sendError,而不像OminiFaces那样调用responseComplete。那么,造成响应“已提交错误”的原因是什么? - Welyab Paula
好的,请参见http://stackoverflow.com/questions/15796497/how-to-send-a-person-to-a-404-page-if-fviewparam-converter-returns-null。我会更新答案。 - BalusC
我已经测试过了,运行得非常好。但是,这些操作的责任是转换器/验证器吗?我认为在验证器/转换器内部放置错误响应非常困难,超出了其职责范围。 - Welyab Paula
我将在后端Bean方法中放置转换器/验证器。现在,这将非常棒。非常感谢。 - Welyab Paula

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接