java中的异常处理

java 异常需要解决一下3个问题:

  1. 哪里发生异常
  2. 谁来处理异常
  3. 如何处理异常

下面就围绕这三个问题来探讨如何才能建立一套完善的异常处理机制:

首先:需要明确在哪里发生异常。在代码中通过try-catch来发现异常,但是有些程序员往往将大段代码定义在一个try-catch块内,这样非常不利于定位问题,是一种不负责任的做法。捕获异常时需要分清稳定代码和非稳定代码,稳定代码值的 是无论如何都不会出错的代码,例如 int a = 0。异常捕获是针对非稳定代码的,捕获时要区分异常类型并做相应的处理。
其次:判断谁来处理异常,在回答这个问题之前,需要明确两个关键字throw和throws的区别,如下是数据访问层生成订单Id的示例代码

	public Long generateOrderId(Long UserId) throw DAOException{
		try {
			return orderIdSequence.nextValue*1000;
		} catch (Exception e) {
			throws new DAOException("Sequence error,userId="+userId,e);
		}
	}

在于数据库交互时可能发生网络连接不同、数据库锁超时、插入数据失败的异常,向上归为DAOException异常。这里的throw 是方法内部抛出具体异常类对象的关键字。而throws则在方法签名上,表示方法调用者可以通过此方法声明向上抛出异常对象。

如果异常在当前方法的处理能力范围之内且没有必要对外透出,那么久直接捕获异常并做相应处理;否则向上抛出,由上层方法或者框架来处理。

最后,无论采用哪种方式处理异常,都严禁捕获异常后什么都不做或打印一行日志了事。如果在方法内部处理异常,需要根据不同的业务进行定制处理,如重试回滚等事件。如果向上抛出异常,需要在异常对象中添加上下文参数、局部变量、运行环境等信息,这样有利于排查问题。

1、异常分类

JDK中所有异常都是Throwable的子类,分为Error(致命异常)和Exception(非致命异常)。Error是一种非常特殊的异常类型,它的出现标识着系统发生了不可控的错误,例如StackOverflowError、OutOfMemoryError。针对此类错误,程序无法处理,只能人工介入。Exception又分为checked异常(受检异常)和unchecked异常(非受检异常)。

checked 异常需要在代码中显示处理的异常,否则会编译出错。如果能自行处理则可以在当前方法中捕获异常;如果无法处理,则继续向调用方抛出异常对象。常见的checked异常包括JDK中定义的SQLException、ClassNotFoundException等。checked异常可以进一步细分为两类:

  • 无能为力、引起主义型。针对此类异常,程序无法处理,如字段超长等导致的SQLException,即使做再多的重试对解决异常也没有任何帮助,一般处理此类异常的做法是完整的保存异常现场,供开发工程师介入解决。
  • 力所能及、坦然处置型。如发生未授权异常(UnAuthorizedException),程序可跳转至权限申请页面
    在Exception 中,unchecked 异常时运行时异常,他们都继承自(RuntimeException),不需要程序显示的捕捉和处理,unchecked异常可以进一步细分为3类;
  • 可预测异常(Predicted Exception):常见的可预测异常包括IndexOutOdBoundException、NullPointerException等,基于对代码的性能和稳定性要求,此类异常不应该被产生或者抛出,而应该提前做好边界检查、空指针判断等处理。显示的声明或者捕获此类异常会对程序的可读性和运行效率产生很大影响。
  • 需捕捉异常(Caution Exception),例如在Dubbo框架进行RPC调用时产生的远程服务超时异常DubboTimeoutException,此类异常时客户端必须显示处理的异常。不能因为服务端的异常导致客户端不可用,此时处理方案可以是重试或者降级处理。
  • 可透出异常(Ignored Exception), 主要是指框架或者系统产生的且会自行处理的异常,而程序无需关心,例如针对Spring框架中抛出NoSushRequestHandingMethodException异常,Spring框架会自己完成异常的处理,默认将自身抛出的异常自动映射到合适的状态码,比如启动防护机制跳转到404页面。
    综上所述,异常分类结构如下:

java中的异常处理

不论哪一种异常,如果需要向上抛出 ,推荐的做法是根据当前场景自定义具有业务含义的异常,为了避免异常泛滥,可以优先使用业界或者团队已定义过的异常。例如远程服务调用中发生服务超时会抛出自定义的 DubboTimeoutException,而不是直接抛出RuntimeException ,更不是抛出Exception或Throwable。

2.异常的抛与接

  • 对外提供的开放接口使用错误码
  • 公司内部跨应用远程服务调用优先考虑使用Result对象来封装错误码、错误描述信息;
  • 应用内部则推荐直接抛出异常对象

3.异常日志打印

  1. 记录异常时一定要输出异常堆栈,例如logger.error(“XXX”+e.getMessage(),e);
  2. 日志中如果输出对象实例,要确保实例类重写了toString方法,否则只会输出对象的hashCode值,没有实际意义