在 Java EE 前端方法中处理服务层异常

Handling service layer exception in Java EE frontend method

我维护一个 Web 应用程序,它有一个页面带有 JSF 标签 <f:event。我在服务 class 中重写了一个方法,使其抛出业务异常。但是,当抛出业务异常时,它并没有被托管bean捕获,而是显示在页面上。似乎我的代码 try/catch 不起作用。

在 XHTML 中:

<f:event listener="#{resourceBean.init(enrollment)}" type="preRenderView" />

托管 Bean 中的侦听器方法:

private boolean canCreateResource;

public void init(Enrollment enrollment) {
    (...)

    try {
        canCreateResource = resourceService.canCreateResource(enrollment);
    } catch (BusinessException e) {
        canCreateResource = false;
    }
}

服务中的方法class:

public boolean canCreateResource(Enrollment enrollment) {
    if (...) {
        if (mandateService.isCoordinator(user, course)) {
            return true;
        } else {
            throw new BusinessException("Undefined business rule.");
        }
    }

    return false;
}

根据我在其他网站上阅读的内容,我想我必须实现一些 JSF 的处理程序 class。但是哪个以及如何?


已编辑

OBS 1:BusinessException class 扩展了 RuntimeException class。

OBS 2:已创建属性 canCreateResource 来控制按钮的呈现。

如果 isCoordinator 方法最终会抛出异常,您应该在 canCreateResource 方法中添加一个 try catch 块。您可以抛出自己的异常或传播原始异常。在这两种情况下,您都必须在方法签名中声明它。如果你抛出 BusinessException:

public void canCreateResource(Enrollment enrollment) throws BusinessException

不要return任何值。或者 return 布尔值但不抛出任何异常。

init 方法内的 catch 块中添加 Facelet 消息异常:

...
} catch (BusinessException e) {
        this.canCreateResource = false;
    FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), ""));
}
}

同样在您的页面中,您必须添加 <h:messages> 标签。

这是因为您从 EJB 中抛出 RuntimeException

当这样的 RuntimeException 没有用 @ApplicationException 注释时,EJB 容器会将其包装在 javax.ejb.EJBException 中并重新抛出。这样做是因为运行时异常 通常 仅用于指示代码逻辑中的错误,即程序员的错误而不是最终用户的错误。你知道的,NullPointerExceptionIllegalArgumentExceptionIndexOutOfBoundsExceptionNumberFormatException 和朋友们。这允许 EJB 客户端对此类运行时异常有一个包罗万象的点,例如 catch (EJBException e) { There's a bug in the service layer or in the way how we are using it! }

如果您尝试过 catch (Exception e) 并检查了实际的异常,那么您会注意到这一点。

相应地修复您的 BusinessException class 以添加该注释,然后它将被识别为真正的应用程序异常,而不是包含在 EJBException:

@ApplicationException(rollback=true)
public class BusinessException extends RuntimeException {
    // ...
}

请注意,如果你抛出一个非 RuntimeException,那么你仍然需要保留注释,明确地使用 rollback=true,因为默认情况下它不会执行回滚,与没有注释的 RuntimeException 相反。

@ApplicationException(rollback=true)
public class BusinessException extends Exception {
    // ...
}

总结:

    从事务性 EJB 方法抛出的
  1. RuntimeException 将执行完全回滚,但异常将被包装在 EJBException.
  2. 中 来自事务性 EJB 方法的
  3. RuntimeException@ApplicationException 将仅在显式设置 rollback=true 时执行完全回滚。
  4. Exception 来自事务性 EJB 方法将不会执行完全回滚。
  5. Exception with @ApplicationException from transactional EJB method 将仅在显式设置 rollback=true 时执行完全回滚。

请注意,@ApplicationException 继承自自定义异常的所有子class,因此您无需对所有子项重复。最好将其作为摘要 class。另请参阅下面链接的相关问题中的示例。

另请参阅:

如果您想捕获不是您自己创建的异常(并且您无法使用 @ApplicationException 进行注释),您可以捕获所有异常并查看原因之一是否是您要捕捉的类型。

可以递归查看异常原因:

public static <T extends Throwable> T getCauseOfType(final Throwable throwable,
                                                     final Class<T> type) {
  if (throwable == null) {
    return null;
  }
  return type.isInstance(throwable) ? (T) throwable : getCauseOfType(throwable.getCause(), type);
}

public static <T extends Throwable> boolean hasCauseOfType(final Throwable throwable,
                                                           final Class<T> type) {
  return getCauseOfType(throwable, type) != null;
}

您可以这样使用:

try {
  ...
}
catch (Exception e) {
  if (hasCauseOfType(e, SomeException.class)) {
    // Special handling
  }
  else {
    throw e;
  }
}