Java Exception 应用情景(一)
Java Exception 应用情景1:用户登录
经常在登录时,遇到“用户名或密码错误”这样的提示,让人搞不清楚究竟是用户名记错了,还是密码输错了。那么为什么程序员不将这两种情况分开提示呢?
当Control层(如,servlet)直接调用Model层的"boolean login(String username, String password)"方法,login函数在用户名密码均正确时,返回true,否则返回false。编写Control层代码的程序员可以通过判断返回 的值来判断登录是否成功。但是,这样只能知道用户是否登录成功,但万一不成功,却无法获知登录失败的原因。
按照传统的做法,有两种方式可以进行“用户名不存在”和“密码错误”的分开提示:
1、在 Control层先调用Model层的 "boolean checkUsernameExist(String username)" 方法,判断用户名是否存在,若不存在则提示“用户名不存在”。若存在则将用户名和密码一起传入Model层的login方法进行函数调用,此时如果 login方法仍返回false,说明密码错误。
优势:不用改动Model层的方法就可以进行两种提示
弊端:Control层本应负责数据的打包转发(从view层接收数据,封装后转给Model层;从Model层获得回应,将数据重新组织后传给View层显示),但现在却混入了逻辑判断,Control层的职责变得有些含糊不清。
2、改动Model层的login方法,让它的返回值可以包含多种信息(如,“int login(String userName, String password)”,返回值0代表成功,1代表)。
优势:不需要Control层事先做额外的判断,只需要分析login函数的返回值就可以知道登录是否成功,以及不成功时的原因
弊端:需要Control层的程序员查看文档来获知Model层程序员对返回值的定义,API不够友好。
现在,让我们来分析一下login的流程:
正常业务逻辑中的意外情况(会妨碍业务的正常进行的事件)就属于的异常情况,如果这个异常情况是可预知的,那么就应该定义为Checked Exception,比如用户名不存在或密码错误这种情况就属于可以预见的意外情况。
异常处理的两项重要内容分别是:异常恢复和异常记录。
异 常恢复的策略针对不同的异常原因而各有不同,例如数据库服务器连接失败时,可以自动间歇性重试;用户输入数据错误时,可以提示用户输入正确的数据,等等。 而异常记录则是相同的,一般是直接调用系统的日志插件进行记录,如logback的Logger对象的error方法,等等。
那么我们可以设计一个父类,称为AppException,这个父类中完成异常处理的共性事务,如查询错误码对应的错误信息、记录错误、传递(保留)错误链等。因此,AppException可以定义如下:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义异常的父类,所有异常均集成自此类. * * @author Elar * @version 2013.2.28 * */ public class AppException extends Exception { private static final long serialVersionUID = 1L; private String errorCode; private String errorInfo; public AppException(String errorCode, Exception e, String className){ this.errorCode = errorCode; this.errorInfo = ErrorCode.getErrorCodeInfo(errorCode); this.initCause(e); log(className); } private void log(String className){ Logger logger = LoggerFactory.getLogger(className); logger.error(errorCode + ": " + errorInfo); } }
其中,ErrorCode类用来获得错误码对应的错误信息。
FailAccessToDBException、UserNameUnavailableException、PasswordErrorException均继承自AppException,以UserNameUnavailableException为例,可以定义为:
/** * 用户名不可用异常 * @author Elar * */ public class UserNameUnavailableException extends AppException { private static final long serialVersionUID = 1L; public UserNameUnavailableException(String errorCode, Exception e, String className){ super(errorCode, e, className); } }
代码中,之间调用父类的构造函数,完成异常处理的共性事务。
此时,Model层的login方法可以改写为:
/** * 用户登陆 * @param username 用户ID * @param pw 密码 * @throws FailAccessToDBException * @throws IllegalUserNameException * @throws PasswordErrorException */ public void login(String username, String pw) throws IllegalUserNameException, PasswordErrorException, FailAccessToDBException { UserBean userBean = null; try { userBean = this.attAdminDao.findByAdminID(username); } catch (FailAccessToDBException e) { throw e; } if (userBean == null) { //EC01001 景区管理员用户名错误 throw new IllegalUserNameException("EC01001",new Exception(), this.getClass().getName()); } else { if (userBean.getPassword().equals(pw)) { logger.info("用户" + username + "登陆"); } else { throw new PasswordErrorException("EC01002",new Exception(), this.getClass().getName()); } } }
这 样,Controller层的程序员在调用Model层代码时,IDE会自动提示这个login函数有可能抛出异常,希望程序员可以应对,这样程序员就不 用担心调用的代码发生意外时,自己不知道而导致错误了。Checked Exception相当于显示的提示了调用代码的人:“我这个代码中可能会出现的意外情况,请你做好准备”。
在servlet中,就会收到IDE的提示,从而针对不同的意外情况作出相应的处理:
try { userManager.login(userName, password); // 将用户名放入session中 RequestDispatcher requestDispatcher = req.getRequestDispatcher("prepareHomePage.do"); requestDispatcher.forward(req, resp); } catch (IllegalUserNameException e) { // 用户名错误 String notice = "用户名错误"; req.setAttribute("notice", notice); RequestDispatcher requestDispatcher = req.getRequestDispatcher("login.jsp"); requestDispatcher.forward(req, resp); } catch (PasswordErrorException e) { // 密码错误 String notice = "密码错误"; req.setAttribute("notice", notice); RequestDispatcher requestDispatcher = req.getRequestDispatcher("login.jsp"); requestDispatcher.forward(req, resp); } catch (FailAccessToDBException e) { // 无法连接到数据库服务器 String notice = "网络错误,请您稍后再试"; req.setAttribute("notice", notice); RequestDispatcher requestDispatcher = req.getRequestDispatcher("login.jsp"); requestDispatcher.forward(req, resp); }
(这段代码有很多冗余的地方,不知道各位看官有没有好的解决方法……)
总结:
java exception是java独特的一个元素,在我理解中,它用来保证我们将精力放在正常的业务逻辑上,一般的代码本身应该主要用来实现正常业务逻辑的执 行。在正常业务逻辑执行的过程中,会产生可预知及不可预知的意外情况,例如内存泄露属于不可预知的意外情况,参数输错属于可预知的意外情况。从前我们都将 exception想得太严重,觉得是非法参数、数组越界这些很严重的情况才会用到exception,属于“不得以而处理之”的东西。现在,我们是否应 该好好利用这个java中特别的一份子,来帮助我们更好的处理业务流程,将正常流程和意外情况分割开来进行处理,以保证正常业务需求与意外事件分离。
使用异常还有一个好处是,它显示的声明了可能发生的意外情况。例如,在读取一个数据项是,也许给出的索引是错误的,在从前的代码中,比较细心些的程序员 会留个心眼检查一下调用的代码是否会返回null对象。有一些粗心的程序员会忘记这一点,默认对方会返回一个对象,然后就在自己的代码中直接调用该对象的 方法,从而在调试时,会莫名其妙的收到空指针异常。如果下层代码在撰写时对索引无效这一个意外情况封装了一个Checked类型的Exception,那 么上层调用的程序员就可以在IDE的提示下获知这一可能发生的意外,从而在代码中避免调用空指针的方法而导致错误。并且,上层程序员还可以视情况的恢复异 常,即使他无能为力,至少还可以再抛给上层,看看有没有人可以处理这个意外。也算是程序员之间,一种不见面的契约。
但是使用异常会有一 个问题,即接口的耦合度变高了。原来只需要参数符合,现在还强制要求处理预见的异常,很多程序员也觉得是一种负担,代码上看起来也会不简洁。而且,如果某 一个业务模块会发生的意外很多,而针对每一个意外情况都定义一种异常的话,会导致接口抛出一长串异常,使代码变得很难看。所以,如何将性质相近的异常抽象 成一个通用的异常也很考验java程序员的功力。
此文是抛砖引玉,向大家介绍小女对java异常的一些看法以及在工程中的一些用法。还希望大家可以多提意见,一起来讨论看看java异常应该怎么用比较合适。希望看官们可以不吝赐教,大家共同进步。