我正在开发一个非常小的测试来模拟3层系统,这样我就能理解异常是如何工作的。同时我想提出一个合理的方法,以便我可以将此设计作为其他应用程序中类似操作的进一步参考。
我一直在阅读有关该主题的不同文章,似乎对使用已检查或未经检查的异常存在巨大争议,这使我对我的最终设计产生怀疑。
我不会通过批评或支持已检查/未检查异常的论据,因为可能它们都是众所周知的,但我会提出我的设计,寻找有关如何改进和制作它的一些建议(只要可能)类似于真实的应用程序。
系统负责使用JDBC在关系数据库(简称MySQL)中执行基本的CRUD操作。我有以下内容:表示层,服务层和持久层。
根据这个答案Handling Dao exceptions in service layer,我有理由不公开特定的层实现并解耦层。所以我决定创建自定义异常并将它们包装到每层的基本异常中,这样我就可以将特定的层异常(即SQLException)转换为一般的层异常(即PersistentException,BusinessException)。如果稍后实现更改,我可以简单地将新的一个包装到更高层所期望的基本异常中。所以我有以下例外:
com.user.persistent.exceptions
|_ PersistentException
|_ ExistingUserException
|_ Some more..
com.user.business.exceptions
|_ BusinessException
|_ somemore....
来自Josh Bloch的书Effective Java:“使用 检查调用者可合理预期的条件的异常 恢复。“我也不确定,但我相信用户可以从SQLExeption恢复(即用户错误提供现有ID,他可以重新输入正确的ID)所以我决定先做例外检查异常。以下是类的外观概述:
持久层。
public interface UserDAO
{
public void create(User team) throws PersistentException;
}
//Implementation in DefaultUserDAO.java
@Override
public void create(User team) throws PersistentException
{
try
{
System.out.println("Attempting to create an user - yikes the user already exists!");
throw new SQLIntegrityConstraintViolationException();
}
catch(SQLIntegrityConstraintViolationException e)
{
throw new ExistingUserException(e);
}
catch (SQLException e)
{
throw new PersistentException(e);
}
finally
{
//close connection
}
}
服务层:
public interface UserService
{
void create(User user) throws BusinessException;
}
//Implementation of UserService
@Override
public void create(User user) throws BusinessException
{
System.out.println("Doing some business logic before persisting the user..");
try
{
userDao.create(user);
}
catch (PersistentException e)
{
throw new BusinessException(e);
}
}
演示
try
{
userService.create(user);
} catch (BusinessException e)
{
e.printStackTrace();
}
现在,以下几点让我对这种设计感到不确定。
这些实际上是我的主要关注点,让我想知道这是不是一个好的设计。我想听听你对它的看法(也许是一些示例代码的小片段)。你们能给我一些关于如何改进它的提示吗?在某种程度上,我可以实现层之间的分离,并避免泄漏层特定的问题..
答案 0 :(得分:4)
我相信您的应用程序在开发完成时不应处理SQL异常。所以,你不应该捕获像SQLException这样的异常。或者,如果您被迫捕获它们,那么只需重新抛出RuntimeException。从我的荣誉经验来看,如果你为别人开发某种类型的库,那么创建你自己的例外只会产生。即使在这种情况下,在许多情况下您也可以使用现有的例外。尝试开发而不创建例外。只有当你意识到没有它们时才能创造它们。
答案 1 :(得分:2)
我的想法:
关于1 - 是的,检查过的例外添加“杂乱代码” - 这是一种权衡,你需要考虑什么对你更重要。
在许多设计中,没有一个完美的解决方案,你必须决定什么更适合你。
关于BusinessException - 我个人不喜欢它。
我想在客户端知道,当我添加用户时,它已经存在。
我不想写一个代码“Peals”BusinessException来获取Root的原因。
一般建议 - 请使用泛型用于crud例外。
例如,请勿使用UserAlreadyExistsException
,而是使用EntityAlreadyExistsException<User>
答案 2 :(得分:1)
@Bartzilla:我也不喜欢在每一层中包装和解包异常对象,它真的使应用程序代码混乱。 我宁愿考虑错误代码和错误消息方法更好的方法。 我认为这个问题有三种解决方案:
1)在应用程序定义的RunTimeException类中包装DB层异常。此RuntimeException应包含错误代码字段,错误消息和原始异常对象。因为所有DAO API都只会抛出运行时异常,所以它意味着业务层不一定需要捕获它。它将被允许冒泡直到处理它的意义。 例如。
class DAOException extends RuntimeException{
private int errorCode;
private String errorMessage;
private Exception originalException;
DAOException(int errorCode, String errorMessage, Exception originalException){
this.errorCode=errorCode;
this.errorMessage=errorMessage;
this.originalException=originalException;
}
}
现在以这种方式,您的DAO方法将根据异常决定错误代码,例如: -
int Integrity_Voildation_ERROR=3;
public void create(User team) throws DAOException
{
try
{
System.out.println("Attempting to create an user - yikes the user already exists!");
throw new SQLIntegrityConstraintViolationException();
}
catch(SQLIntegrityConstraintViolationException e)
{ int errorCode=Integrity_Voildation_ERROR;
throw new DAOException(Integrity_Voildation_ERROR,"user is already found",e);
}
}
此异常可以在应该存在的层中捕获。在这种情况下,每个错误代码表示可恢复(可操作)的异常。 当然,应用程序的入口点(servlet或过滤器或任何东西)必须捕获一般的异常以捕获不可恢复的异常并向用户显示有意义的错误。
2)让你的DAO API返回一个Result类型的对象,该对象包含与DAOException中提供的信息相同的信息。
所以你有Result类: -
Class Result implements IResult{
private boolean isSuccess;
private int errorCode;
private String errorMessage;
private Exception originalException;//this might be optional to put here.
}
So a DAO API:
public IResult create(User team) throws DAOException
{
IResult result=new Result();
try
{
System.out.println("Attempting to create an user - yikes the user already exists!");
throw new SQLIntegrityConstraintViolationException();
}
catch(SQLIntegrityConstraintViolationException e)
{ int errorCode=Integrity_Voildation_ERROR;
result.setSuccess(false);
result.setErrorCode(errorCode);
result.setErrorMessage("user is already found");
}
return result;
}
在上面的例子中,约束是每个DAO API需要返回相同的Result Object。当然,您的业务相关数据可以填充在ResultClass的各个子类中。 请注意,在情况1和2中,您可以使用枚举来定义所有可能的错误代码。可以从数据库表等中提取错误消息。
3)如果你想避免使用errorCode,你可以这样做: 您可以为每种可能的可恢复SQL异常类型定义一个异常层次结构,而不是像DAOException(上面的case1)那样定义单个RunTimeException类,每个异常层次结构都是父类DAOException的子类。
基于java的spring框架DAO异常层次结构就是一个很好的例子。请浏览this。
答案 3 :(得分:1)
基于约定优于配置概念,你可以设计你的异常处理程序,我认为最快和最可靠的方法是使用AOP,为此你可以根据异常类型处理你方面的异常,你可以做一个决定是否从异常中恢复。 例如,如果发生验证异常,您可以将输入页面路径返回到将客户端发送到现有页面以正确填充数据,如果发生不可恢复的异常,您可以在异常处理程序中返回错误页面。
您可以为输入和错误页面创建约定,例如,为输入和错误页面提供全局名称。通过这种方式,您可以根据异常类型决定将请求发送到适当的页面。
您可以关注Aspect Oriented Programming
的这篇文章下面添加的样本
@Aspect
public class ExceptionHandlerAspect {
@Around("@annotation(ExceptionHandler)")
public Object isException(ProceedingJoinPoint proceedingJoinPoint) {
try {
return proceedingJoinPoint.proceed();
} catch (Exception exception) {
if (exception instanceof UnRecoverableException) {
addErrorMessage("global system error occurred");
return "errorPage";
} else if (exception instanceof ValidationException) {
addErrorMessage("validation exception occurred");
return "inputPage";
} else {
addErrorMessage("recoverable exception occurred");
return "inputPage";
}
}
}
}
@Target({METHOD})
@Retention(RUNTIME)
public @interface ExceptionHandler {
}
表示层
@ExceptionHandler
public String createUser() {
userService.create(user);
return "success";
}
答案 4 :(得分:1)
看看spring-jdbc是如何做到的。我认为这是一个非常好的设计。 Spring处理来自驱动程序层的已检查异常并抛出未经检查的异常。它还将MySQL,Postgres等不同的异常翻译成标准的弹簧。
在过去的6年中,我已经在所有代码中切换到未经检查的异常,并且没有回头。 90%的时间你无法处理这个法典。你所知道的情况,无论是通过设计还是通过测试,并将相关的捕获块放入其中。
答案 5 :(得分:0)
你说3层系统 - 这是否意味着这些图层可能在不同的计算机上运行或在不同的进程中运行? 要么 它只是将代码组织到由特定函数定义的一组层中,以及在单个节点上运行的所有内容吗?
问这个的原因 - 除非你的图层在不同的进程中运行 - 它没有为CheckedExceptions添加任何值。拥有大量CheckedExceptions只会使代码混乱并增加不必要的复杂性。
异常设计很重要,并且做了类似ag112使用的方式,例如使用RunTimeException是干净的,代码少且易于管理。
..只是我的想法