在 JSF 中,被调用的 EJB 抛出的自定义异常被视为 EJBTransactionRolledBackException 或 NullPointerException 或 ServletException

Bil*_*mus 2 java ejb exception custom-exceptions jsf-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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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

有人可以为我指出正确的方向,让客户端/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);
    }
  }
Run Code Online (Sandbox Code Playgroud)

自定义异常:

public class LoginPreviouslyConfirmedException extends RuntimeException {

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

    public LoginPreviouslyConfirmedException(String msg) {
        super(msg);
    }
}
Run Code Online (Sandbox Code Playgroud)

它位于 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();
      }
Run Code Online (Sandbox Code Playgroud)

这是日志显示的内容:

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
Run Code Online (Sandbox Code Playgroud)

然后有一些线索:

SEVERE: Including this to see if the previously confirmed exception was skipped over
javax.ejb.EJBTransactionRolledbackException
Run Code Online (Sandbox Code Playgroud)



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

public void fetchChallengeResponse() throws LoginPreviouslyConfirmedException{
     crespList = regFacade.fetchChallengeResponse(confirmUuid);
     this.setChallengeQuestion();
  }
Run Code Online (Sandbox Code Playgroud)

这是在 web.xml 中

<error-page>
    <exception-type>com.extremelatitudesoftware.els_commons.exceptions.LoginPreviouslyConfirmedException</exception-type>
    <location>/errorpages/already_confirmed.xhtml</location>
</error-page>
Run Code Online (Sandbox Code Playgroud)

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

Bil*_*mus 5

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

例如

@ApplicationException(rollback=true)
public class LoginPreviouslyConfirmedException extends Exception {...
Run Code Online (Sandbox Code Playgroud)

根本问题是,在将异常传递给客户端之前,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 中使用自定义异常?

无论如何,这有效。只是希望我能早点在谷歌上找到正确的搜索词。