2

这就是问题所在:EJB 抛出了这个异常(来自 glassfish 日志):

SEVERE: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
    at com.extremelatitudesoftware.security.auth.CredentialsController.checkForPreviousConfirmation(CredentialsController.java:244)

在异常堆栈的客户端(在底部)我看到了这个:

WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
    at com.extremelatitudesoftware.accesscontrol.registration.RegistrationConfirmationBean.setChallengeQuestion(RegistrationConfirmationBean.java:61)
    at com.extremelatitudesoftware.accesscontrol.registration.RegistrationConfirmationBean.fetchChallengeResponse(RegistrationConfirmationBean.java:51)

当然,在浏览器中,我得到一个通用的 500 异常错误,表示存在空指针异常。

我想要一个自定义错误页面来检测“com.extremelatitudesoftware.els_commons.exceptions.LoginPreviousConfirmedException”并说“哎呀,你已经确认了!”

有人可以为我指出正确的方向,让客户端/JSF 层正确地“看到”异常,以便可以做到这一点。

这是问题的用例:
我在 EJB 层中有一个方法,用于检查用户之前是否确认了他们的登录帐户。如果他们没有,它会处理他们确认新帐户的请求。如果他们说回到旧电子邮件并说,嗯,让我们再次单击它,后端将检测到,抛出异常以使其被 JSF/客户端层拾取并通知用户该帐户已被确认。


添加示例代码:

方法检查条件并在必要时抛出异常。请注意,大多数情况下这不会发生,所以我想使用这种机制,而不是在每次有人确认他们的帐户时都拨打不必要的电话。

...
private void checkForPreviousConfirmation(Credentials cr) 
                                    throws LoginPreviouslyConfirmedException {

    if (!(CredentialsStatusType.PENDING.equals(cr.getStatus()))
         || !(CredentialsDispositionType.WAITING.equals(cr.getDisposition()))) {
      String msg = "Attempting to confirm previously confirmed login using "
              + "confirmation UUID: " + cr.getConfirmationUuid();
      Logger.getLogger(CredentialsController.class.getName()).log(Level.INFO,
              msg);
      throw new LoginPreviouslyConfirmedException(msg);
    }
  }

自定义异常:

public class LoginPreviouslyConfirmedException extends RuntimeException {

    public LoginPreviouslyConfirmedException(String msg, Throwable cause) {
        super(msg, cause);
    }

    public LoginPreviouslyConfirmedException(String msg) {
        super(msg);
    }
}

这是在 JSF 层的 ManagedBean 中。它通过以下方式调用:preRenderView

        ...
public void fetchChallengeResponse() {
    try {
         crespList = regFacade.fetchChallengeResponse(confirmUuid);
    } catch (LoginPreviouslyConfirmedException ex) {
         String msg = "Attempting to confirm previously confirmed login using " + "confirmation UUID: " + getConfirmUuid();
         Logger.getLogger(RegistrationConfirmationBean.class.getName()).log(Level.SEVERE, msg, ex);
          FacesContext facesContext = FacesContext.getCurrentInstance();
          Application application = facesContext.getApplication();
          NavigationHandler navigationHandler = application.getNavigationHandler();
          navigationHandler.handleNavigation(facesContext, null, "faces/errorpages/already_confirmed.xhtml");
          facesContext.renderResponse();
        }catch (Exception ex){
          String msg = "Including this to see if the previously confirmed exception was skipped over";
              Logger.getLogger(RegistrationConfirmationBean.class.getName()).log(Level.SEVERE, msg, ex);
        }

        this.setChallengeQuestion();
      }

这是日志显示的内容:

INFO: Attempting to confirm previously confirmed login using confirmation UUID: b90b33ca-dc69-41c1-9c60-99152810c89b
WARNING: EJB5184:A system exception occurred during an invocation on EJB CredentialsController, method: public java.util.ArrayList com.extremelatitudesoftware.security.auth.CredentialsController.fetchChallengeResponseByCredentialUuid(java.lang.String) throws com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException
WARNING: javax.ejb.TransactionRolledbackLocalException: Exception thrown from bean

然后有几个追踪:

SEVERE: Including this to see if the previously confirmed exception was skipped over
javax.ejb.EJBTransactionRolledbackException



根据 BalusC 的建议,我将 ManagedBean 中的代码更改为:

public void fetchChallengeResponse() throws LoginPreviouslyConfirmedException{
     crespList = regFacade.fetchChallengeResponse(confirmUuid);
     this.setChallengeQuestion();
  }

这是在 web.xml

<error-page>
    <exception-type>com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException</exception-type>
    <location>/errorpages/already_confirmed.xhtml</location>
</error-page>

但我仍然得到这个(这是显示的错误页面的图像)。

4

2 回答 2

3

这是我今天通过研究找到的最简单最好的答案:使用 @ApplicationException(rollback=true) 注释装饰自定义异常。

例如

@ApplicationException(rollback=true)
public class LoginPreviouslyConfirmedException extends Exception {...

根本问题是,在将异常传递给客户端之前,EJB 容器至少将一些异常包装在“EJBTransactionRolledBackException”包装器中。我不确定,但它似乎类似于 JSF 想要将异常包装在“ServletException”中的方式,以处理那里引发的任何事情。在我看来,隐藏真正的应用程序错误是非常有缺陷的。在任何情况下,它都会阻止客户端“看到”您可能抛出的任何类型的特殊情况异常,原因是您或您的用户可以在不中止整个操作的情况下进行更正。也就是说,它将看到的只是用于包装大量异常的 EJBTransactionRolledBackException。这样做会导致无法轻松调试或处理异常。幸运的是,在最新一期的 JEE 中,它们包含了 @ApplicationException 注释来抵消这种行为。虽然我不明白为什么他们不完全停止包装异常。

在任何情况下,@ApplicationException(rollback=true) 告诉服务器不要将异常包装在 EJBTransactionRolledBackException 中并将其按原样传递回客户端。这允许客户端查看真正的异常是什么,以便它有机会优雅地处理它。指定“rollback=true”告诉服务器回滚当前事务。如果你不这样做,它默认为 false 并且只会将所有内容放在那里。以任何状态结束这一切。

如果自定义异常继承自 Exception,并且 EJB 会话 bean 中的方法抛出它,那么在 JSF ManagedBean 中进行调用的方法也必须抛出它。如果它继承自 RuntimeException,则不会。

在任何情况下,异常都不会被 EJBTransactionRolledBackException 包装,因此当它返回到 JSF 层时,将调用 web.xml 中为自定义异常定义的异常页面。无需在托管 bean 中捕获异常。无需过滤。不需要特殊的导航规则。但是,如果您确实想抓住它,它可以被抓住。没有@ApplicationException,它就不会(因为它被包装了)。

在这里找到的信息(在其他地方):
http ://docs.oracle.com/javaee/5/api/javax/ejb/ApplicationException.html
http://openejb.apache.org/examples-trunk/applicationexception/
如何在会话 Bean 中使用自定义异常?

无论如何,这行得通。只是希望我能早点在 Google 上找到正确的搜索词。

于 2012-06-08T03:09:48.073 回答
1

在引发异常的 EJB 方法中,声明throws LoginPreviouslyConfirmedException. 然后,在托管 bean 中,使用针对该异常的 try-catch 包围 EJB 调用。在 catch 块中;您可以将用户重定向到错误页面;或者只是向视图发送消息(在 h:messages、p:growl 等中呈现)。

于 2012-06-06T12:00:14.290 回答