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可以定义如下:

View Code
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为例,可以定义为:

View Code
/**
 * 用户名不可用异常
 * @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方法可以改写为:

View Code
/**
     * 用户登陆
     * @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的提示,从而针对不同的意外情况作出相应的处理:

View Code
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异常应该怎么用比较合适。希望看官们可以不吝赐教,大家共同进步。

原文地址:https://www.cnblogs.com/elaron/p/2939062.html