异常与日志
异常捕获是针对非稳定代码的,捕获时要区分异常类型并做出相应的处理。
throw
是方法内部抛出具体异常类对象的关键字,而throws
则用在方法signature
上,表示方法定义者可以通过次方法声明向上抛出异常对象。
异常分类
所有异常都是Throwable
的子类,分为Error
和Exception
。
Error
:标识系统发生了不可控的错误,如StackOverflowError
,程序无法处理,只能人工介入Exception
:checked
:需要在代码中显式捕获的异常,否则会编译出错,包括JDK中定义的ClassNotFoundException
- 无能为力、引起注意型:此类异常程序无法处理,例如字段超长导致的
SQLException
,人工介入 - 力所能及型:如发生未授权异常,可以跳转至权限页面
- 无能为力、引起注意型:此类异常程序无法处理,例如字段超长导致的
unchecked
:运行时异常,都继承自RuntimeException
,不需要程序进行显式的捕捉和处理- 可预测异常:基于对代码的性能和稳定要求,此类不应该抛出,如
NullPointerException
- 需捕获异常:例如在远程调用时失败,需要做相应的处理
- 可透出异常:由框架或系统产生的且会自行处理的异常,程序无须关心
- 可预测异常:基于对代码的性能和稳定要求,此类不应该抛出,如
try代码块
当存在try的时候,可以只有catch或者finally中的一个,但是不能两个都没有。
finally中的代码块即使发生Error
也还是会执行,用于清理资源、释放连接、关闭管道流等操作。
如果finally没有执行,有以下可能:
- 没有进入try语句块
- 进入try语句块,但是代码运行中出现了死循环或死锁状态
- 进入try语句块,但是执行了
System.exit()
操作,也就是说如果在执行try语句块的过程中,终止了程序,此时finally中的语句块还是不会执行
finally语句块注意:
-
finally是在return语句块计算完成后,在程序返回前执行的。
将return的结果暂存起来,待finally代码块执行结束后再将之前暂存的结果返回。
public static int testFinally() { int temp = 100; try { throw new Exception(); } catch (Exception e) { return ++temp; } finally { temp = 0; } }
此时的值是101,而不是0。
-
不要在finally语句中使用return,会导致返回值变得不可控。
try代码块与锁的关系:
lock方法可能抛出unchecked
异常,如果放在try中,必然触发finally中的unlock
方法执行,但是对于未加锁的对象执行unlock
又会有异常,所以需要在try之前调用lock()
方法,避免由于加锁失败导致finally调用unlock()
抛出异常。
错误代码如下:
Lock lock = new Lock();
preHandle();
try {
// 无论加锁是否成功,unlock()都会执行
lock.lock();
something();
} finally {
lock.unlock();
}
日志
日志系统应该监控系统运行状况值,指对服务器使用状态,如内存、CPU等使用情况;应用运行情况,如响应时间,QPS等交互状态;应用错误信息。
日志按重要程度递增依次为:debug、info、warn、error、fatal(出现的错误会导致程序中断)
对于debug和info级别的日志,必须使用条件输出或者占位符的方式打印。原因:如果仅是log.debug("id:" + id + symbol);
,假设打印级别是info,那么此日志不会打印,但是还是会执行字符串拼接和toString()
的操作,造成了资源浪费。
if(logger.isDebugEnabled) {
log.debug("id:" + id + symbol);
}
logger.debug("id:{} {}", id, symbol);
error级别只记录系统逻辑错误、异常或者违反重要的业务规则。
记录异常时需要输出异常堆栈,如果要输出对象实力,确保重写了toString()
,否则智慧输出对象的hashCode
值。
日志框架
采用门面设计模式,由三部分组成:
- 日志门面:门面设计模式只提供一套接口规范,自身不负责日志功能的实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印及具体的使用细节等。
slf4j
、commons-logging
。 - 日志库:具体实现了日志的相关功能。
log4j
、log-jdk
、logback
。 - 日志适配器:
- 日志门面适配器:此前的日志库可能没有实现
slf4j
的接口,所以可能需要使用slf4j+log4j
的模式 - 日志库适配器:老工程用的日志模式未采用门面模式,需要适配
- 日志门面适配器:此前的日志库可能没有实现
新工程推荐:slf4j
+logback
导入依赖+日志配置文件+
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
logger与当前类绑定,避免每次new一个对象。