Handling service layer exception in Java EE fronte

2019-01-04 15:02发布

I maintain a web application that have a page with the JSF tag <f:event. I have rewrote a method in a service class for it to throw a business exception. However, when the business exception is thrown, it isn't caught in managed bean and the exception is showed on the page. Seems that my code try/catch doesn't work.

In XHTML:

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

Listener method in Managed Bean:

private boolean canCreateResource;

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

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

Method in service class:

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

    return false;
}

From what I read on other sites, I suppose I have to implement some JSF's handler class. But which and how?


EDITED

OBS 1: The BusinessException class extends RuntimeException class.

OBS 2: The attribute canCreateResource was created to control the render of a button.

3条回答
唯我独甜
2楼-- · 2019-01-04 15:23

In case you want to catch an exception that you did not create yourself (and you are not able to annotate with @ApplicationException), you can catch all exceptions and see if one of the causes is of the type you want to catch.

You can check the causes of the exception recursively:

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;
}

You can use this like:

try {
  ...
}
catch (Exception e) {
  if (hasCauseOfType(e, SomeException.class)) {
    // Special handling
  }
  else {
    throw e;
  }
}
查看更多
爷、活的狠高调
3楼-- · 2019-01-04 15:25

It's because you threw a RuntimeException from an EJB.

When such RuntimeException is not annotated with @ApplicationException, then the EJB container will wrap it in an javax.ejb.EJBException and rethrow it. This is done so because runtime exceptions are usually only used to indicate bugs in code logic, i.e. programmer's mistakes and not enduser's mistakes. You know, NullPointerException, IllegalArgumentException, IndexOutOfBoundsException, NumberFormatException and friends. This allows the EJB client to have one catch-all point for such runtime exceptions, like catch (EJBException e) { There's a bug in the service layer or in the way how we are using it! }

If you had tried catch (Exception e) and inspected the actual exception, then you'd have noticed that.

Fix your BusinessException class accordingly to add that annotation, it will then be recognized as a real application exception and not be wrapped in an EJBException:

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

Do note that in case you throw an non-RuntimeException, then you still need to keep the annotation on that, explicitly with rollback=true, because by default it wouldn't perform a rollback, on the contrary to a RuntimeException without the annotation.

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

Summarized:

  1. RuntimeException thrown from transactional EJB method will perform full rollback, but exception will be wrapped in EJBException.
  2. RuntimeException with @ApplicationException from transactional EJB method will only perform full rollback when rollback=true is explicitly set.
  3. Exception from transactional EJB method will not perform full rollback.
  4. Exception with @ApplicationException from transactional EJB method will only perform full rollback when rollback=true is explicitly set.

Note that @ApplicationException is inherited over all subclasses of the custom exception, so you don't need to repeat it over all of them. Best would be to have it as an abstract class. See also the examples in the related question linked below.

See also:

查看更多
霸刀☆藐视天下
4楼-- · 2019-01-04 15:26

If isCoordinator method can eventually throw an exception you should add a try catch block inside canCreateResource method. You can throw your own exception or propagate the original one. In both cases you have to declare it in the method signature. If you throw BusinessException:

public void canCreateResource(Enrollment enrollment) throws BusinessException

Do not return any value. Or return a boolean value but do not throw any exception.

In the catch block inside the init method add the Facelet message exception:

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

Also in your page you have to add <h:messages> tag.

查看更多
登录 后发表回答