让表示层 (JSF) 处理来自服务层 (EJB) 的业务异常
Letting the presentation layer (JSF) handle business exceptions from service layer (EJB)
更新提供的实体的 EJB 方法(使用 CMT):
@Override
@SuppressWarnings("unchecked")
public boolean update(Entity entity) throws OptimisticLockException {
// Code to merge the entity.
return true;
}
如果检测到要由调用者(托管 bean)精确处理的并发更新,这将抛出 javax.persistence.OptimisticLockException
。
public void onRowEdit(RowEditEvent event) {
try {
service.update((Entity) event.getObject())
} catch(OptimisticLockException e) {
// Add a user-friendly faces message.
}
}
但是这样做会使 javax.persistence
API 对表示层产生额外的依赖,这是一种导致紧耦合的设计味道。
应该将它包装在哪个异常中,以便可以完全省略 紧耦合 问题?或者是否有一种标准的方法来处理这种异常,而这种异常又不会导致在表示层上强制执行任何服务层依赖性?
顺便说一下,我发现在 EJB 中(在服务层本身)捕获这个异常然后 return 一个标志值给客户端(JSF)是笨拙的。
创建一个自定义服务层特定的运行时异常,用 @ApplicationException
和 rollback=true
注释。
@ApplicationException(rollback=true)
public abstract class ServiceException extends RuntimeException {}
为一般的业务异常创建一些具体的子类,比如约束违反,需要的实体,当然还有乐观锁。
public class DuplicateEntityException extends ServiceException {}
public class EntityNotFoundException extends ServiceException {}
public class EntityAlreadyModifiedException extends ServiceException {}
有的可以直接扔
public void register(User user) {
if (findByEmail(user.getEmail()) != null) {
throw new DuplicateEntityException();
}
// ...
}
public void addToOrder(OrderItem item, Long orderId) {
Order order = orderService.getById(orderId);
if (order == null) {
throw new EntityNotFoundException();
}
// ...
}
其中一些需要全局拦截器。
@Interceptor
public class ExceptionInterceptor implements Serializable {
@AroundInvoke
public Object handle(InvocationContext context) throws Exception {
try {
return context.proceed();
}
catch (javax.persistence.EntityNotFoundException e) { // Can be thrown by Query#getSingleResult().
throw new EntityNotFoundException(e);
}
catch (OptimisticLockException e) {
throw new EntityAlreadyModifiedException(e);
}
}
}
它被注册为默认拦截器(在所有 EJB 上),如下 ejb-jar.xml
。
<interceptors>
<interceptor>
<interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
作为一般提示,在 JSF 中,您还可以有一个全局异常处理程序,它只添加一个 faces 消息。当以 开始时,您可以在 YourExceptionHandler#handle()
方法中执行类似的操作:
if (exception instanceof EntityAlreadyModifiedException) { // Unwrap if necessary.
// Add FATAL faces message and return.
}
else {
// Continue as usual.
}
更新提供的实体的 EJB 方法(使用 CMT):
@Override
@SuppressWarnings("unchecked")
public boolean update(Entity entity) throws OptimisticLockException {
// Code to merge the entity.
return true;
}
如果检测到要由调用者(托管 bean)精确处理的并发更新,这将抛出 javax.persistence.OptimisticLockException
。
public void onRowEdit(RowEditEvent event) {
try {
service.update((Entity) event.getObject())
} catch(OptimisticLockException e) {
// Add a user-friendly faces message.
}
}
但是这样做会使 javax.persistence
API 对表示层产生额外的依赖,这是一种导致紧耦合的设计味道。
应该将它包装在哪个异常中,以便可以完全省略 紧耦合 问题?或者是否有一种标准的方法来处理这种异常,而这种异常又不会导致在表示层上强制执行任何服务层依赖性?
顺便说一下,我发现在 EJB 中(在服务层本身)捕获这个异常然后 return 一个标志值给客户端(JSF)是笨拙的。
创建一个自定义服务层特定的运行时异常,用 @ApplicationException
和 rollback=true
注释。
@ApplicationException(rollback=true)
public abstract class ServiceException extends RuntimeException {}
为一般的业务异常创建一些具体的子类,比如约束违反,需要的实体,当然还有乐观锁。
public class DuplicateEntityException extends ServiceException {}
public class EntityNotFoundException extends ServiceException {}
public class EntityAlreadyModifiedException extends ServiceException {}
有的可以直接扔
public void register(User user) {
if (findByEmail(user.getEmail()) != null) {
throw new DuplicateEntityException();
}
// ...
}
public void addToOrder(OrderItem item, Long orderId) {
Order order = orderService.getById(orderId);
if (order == null) {
throw new EntityNotFoundException();
}
// ...
}
其中一些需要全局拦截器。
@Interceptor
public class ExceptionInterceptor implements Serializable {
@AroundInvoke
public Object handle(InvocationContext context) throws Exception {
try {
return context.proceed();
}
catch (javax.persistence.EntityNotFoundException e) { // Can be thrown by Query#getSingleResult().
throw new EntityNotFoundException(e);
}
catch (OptimisticLockException e) {
throw new EntityAlreadyModifiedException(e);
}
}
}
它被注册为默认拦截器(在所有 EJB 上),如下 ejb-jar.xml
。
<interceptors>
<interceptor>
<interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
作为一般提示,在 JSF 中,您还可以有一个全局异常处理程序,它只添加一个 faces 消息。当以 YourExceptionHandler#handle()
方法中执行类似的操作:
if (exception instanceof EntityAlreadyModifiedException) { // Unwrap if necessary.
// Add FATAL faces message and return.
}
else {
// Continue as usual.
}