父类是Throwable,位于java.lang包中,两大子类:Error和Exception。
Error是程序无法处理的错误,表示运行程序时较严重的错误。
Exception表示本身可以处理的异常,分为运行时异常和非运行时异常(也叫作可查异常和不可查异常),主要是指在编译器能否查出来的异常。常见的运行时异常是NullPointerException,IndexOutBoundsException,常见的非运行时异常是IOException,SQLException等。Error的子类都是运行时异常。
当一个方法出现错误引发异常时,jvm会创建异常对象并交付给运行时系统,运行时系统会依次回查调用栈的方法,一层层的向上寻找处理异常的代码并执行,如果系统遍历调用栈而没有找到处理异常的代码,那么系统就会终止运行,Java程序终止。
系统通过异常处理器来捕获异常,当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,就可以处理该异常。
这里的相符,指方法抛出的异常可以是异常处理器的子类或者是异常处理器本身。
不可查异常,编译时允许不处理;但是为了程序健壮性,常见的(如NullPointerException)要处理;可查异常在编译时必须处理,否则编译无法通过;
可查异常有两种处理方式,一是捕捉异常(try..catch),一是抛出异常(throw);
在方法定义上使用throws关键字来抛出异常,多个异常用逗号分隔;可以抛出Exception和Error。在方法体中使用throw关键字来抛出异常对象,一般使用throw来抛出自定义异常,使用throw后,后面的代码不会被执行。
常见的一些异常,都已抛出,以File类的createNewFile()方法为例:
public boolean createNewFile() throws IOException {
SecurityManager security = System.getSecurityManager();
if(secuirty != null) security.checkWrite(path);
if(isInvalid()){
throw new IOException("Invalid file path");
}
return fs.createFileExclusively(path);
}
调用者可以捕捉异常并处理,如果调用者不想处理,可以继续往上抛,否则编译无法通过;
注意,一定要避免吞异常——捕捉了异常又不处理。
常用的是一个try,后面跟多个catch,每个catch捕获不同的异常。
try{
}catch(IOException e1){
}catch(NullPointerException e2){
}catch(Exception){
}
多个catch块匹配的原则,如果抛出的异常属于catch定义的异常类,或者属于catch定义的异常类的子类,就算匹配。一旦某个catch,捕获到某个异常,将进入异常处理程序,处理结束,就意味着整个异常try-catch结束,其它的catch子句就不再有匹配的机会。
所以,建议定个多个子类来捕获不同的异,要把catch(exception)放到最后,否则后面的catch代码不会执行。
处理方式同try-catch,多了一个finally,表示在catch处理结束之后,会执行finally中的内容。
插句题外话,一般都会说finally中代码一定会被执行,但是实际上却有例外,看下面的例子。
public class DaemonThread extends Thread {
@Override
public void run() {
super.run();
try{
for(int i=0;i<1000;i++){
System.out.println(i);
}
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println("in finally ...");
}
}
}
public static void main(String[] args) {
DaemonThread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
System.out.println("---------");
thread.interrupt();
}
有一个Thread,设置为Deamon的,然后突然结束(interrupt)这个线程,这时线程里的代码会突然停止,finally中代码也不会执行。
项目中捕获了异常之后,如何处理? 常见的错误写法如下:
try{
xxx
}catch(Exception e){
e.printStackTrace();
}
这样写,对用户不友好,有了异常,没有处理;对系统也不友好,有了异常,没有做任何处理,仅仅是打印出异常栈信息,系统后台输出那么多,谁会去看控制台信息?大数据时代,数据很珍贵,异常很珍贵,有了异常,不要浪费掉,要通过日志把它记录下来。
目前比较流行的是使用log4j+commons-logging来记录错误日志。这两个开源框架都属于apache基金会。
使用起来很简单,在项目中增加log4j.properties,引入log4j.jar和commons-logging.jar即可。
maven中增加以下依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j<artifactId>
<version>1.2.16</version></dependency><dependency>
<groupId>common-logging</groupId>
<artifactId>common-loggin<artifactId>
<version>1.2</version></dependency>
log4j是Apache开源的一个日志组件,可以方便的异步记录日志到控制台、文件、数据库等。log4j结构里包含三部分:日志写入器、日志输出终端、日志布局。
日志级别:all<debug<info<warn<error<fatal<off
org.apache.log4j.ConsoleAppender (控制台)
org.apache.log4j.FileAppender(输出到文件)
org.apache.log4j.DailyRollingFileAppender(每天都产生一个文件)
org.apache.log4j.RollingFileAppender(文件大小达到一定尺寸时会产生一个新的文件,文件名自动加上数字)
org.apache.log4j.jdbc.JDBCAppender(将日志信息写入到数据库)
org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
%m:输出代码中指定的消息。 %p:输出优先级。 %r:输入自应用启动到输出该log信息耗费的毫秒数。 %c:输出所属的类目,通常就是所在类的全名。
%n:输出一个回车换行符。Windows平台为“\r\n”,UNIX为“\n”。
%d:输出日志时间点的日期或时间,默认格式为ISO8601,推荐使用“%d{ABSOLUTE}”,这个输出格式形如: “2007-05-07 18:23:23,500”,符合中国人习惯。
%l:输出日志事件发生的位置,包括类名、线程名,以及所在代码的行数。
%t:输出产生该日志线程的线程名。
可以通过properties或xml文件来配置,一般都采用properties来配置,命名为log4j.properties,放在classpath中。配置log4j主要是配置rootLogger和子logger;
首先配置rootLogger,指定输出级别以及Appender。
接着配置每一个Appender(参考之前介绍的Appender)
子logger可以指定类的输出级别
log4J提供了Appender(org.apache.log4j.jdbc.JDBCAppender)可以写入到数据库,十分方便。
下面是一个常用的log4j.properties:
Commons-Logging提供一个Log接口,它提供一个简单的日志抽象,允许开发者使用不同的具体日志实现工具。
它提供对一些日志记录工具的支持:Jdk14Logger,Log4JLogger,LogKitLogger,NoOpLogger,SimpleLog等。
获取方式:
Log logger = LogFactory.getLog(xx.class);
使用时,可以通过
logger.info("");
logger.debug("");
logger.warn("");
logger.error("");
等来记录错误信息,当catch住异常时,可以通过logger.error()来把错误信息记录下来,然后通过输出到文件或者数据库中。
Log获取过程:
1、common-logging首先在CLASSPATH中查找commons-logging.properties文件,该文件至少要定义org.apache.commons.logging.Log属性,如果有此属性,就用其对应的日志组件;
2、如果第一步失败了,则去找环境变量org.apache.commons.logging.Log,如果找到,就是用其对应的日志组件;
3、如果第二步失败了,则去CLASSPATH中寻找log4j的类,如果找到了就使用log4j,当然log4j.properties文件(或xml配置文件)必须存在;
4、如果上述过程均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。
5、最后,如果上述操作都失败(JRE 版本也低于1.4),则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。结束发现过程。
使用Log4J+commons logging方法把错误信息都记录下来,要记录的比较详细;
有了nagios,系统是否运行我们可以知道;
但是系统是否有404、500,系统中有没有错误内容,很难知道;
对于捕获的异常,要保存到文件或者数据库中,记录为错误日志;
通过Tableau等工具来分析错误日志;
部分系统中,出现使用logger.error,但是并没有输出的问题,这时有可能是包发生冲突了,我发现冲突的包有jcl-over-slf4j.jar和log4j-slf4j.jar,删除掉就好。
完。
log4j:http://logging.apache.org/log4j/2.x/
commons-logging:http://commons.apache.org/proper/commons-logging/