Chapter11-异常日志断言和调试

Evernote Export
  1. 异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
  2. 异常对象都派生于Throwable类的一个实例,在下一层立即分解为两个分支:Error和Exception。Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。Exception类又分为程序没问题,由IO制类错误导致的异常IOException和由程序错误导致的RunTimeException。
  3. RuntimeException包含下面及几种情况:错误的类型转换、数组访问越界、访问空指针。不是派生于Runtime Exception的错误有试图在文件尾部后面读取数据、试图打开一个错误格式的URL、试图根据给定的字符串查找Class对象,而这个字符串的类并不存在。
  4. Java将RuntimeException或Error类的异常称为 未检查异常,将IOException等其他异常称为 已检查异常。编译器将检查是否为所有的已检查异常提供了异常处理器。
  5. 声明已检查异常:方法应该在其首部声明所有可能抛出的异常,这样可以从首部反应出这个方法可能抛出哪类已检查异常。在下面四种情况下应抛出异常:
    • 调用一个已抛出已检查异常的方法;
    • 程序运行过程中发现,并且利用throw语句抛出一个已检查异常;
    • 程序出现错误;
    • Java虚拟机和运行时库促会先内部异常
  6. 总之,一个方法 必须声明所有的可能抛出的已检查异常,而未检查异常是不可控制或可以避免的。如果方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误信息。
  7. 如果子类中覆盖了超类的一个方法,子类方法中声明的已检查异常不能超过超类方法中声明的异常范围。
  8. 抛出异常:对于一个已经存在的异常类,将其抛出非常容易:1.找到合适的异常类;2。创建一个这个类的对象;3.将对象抛出。
  9. 创建异常类:如果遇到任何标准异常类都没有能够充分描述清除的问题。在这种情况下,就要创建自己的异常类。例如:定义一个派生与IOException的类,习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,在调试中非常有用)。
  10. 要想捕获一个异常,必须设置try/catch语句块。最简单的语句块如下:
try{
    code
}catch(ExceptionType e){
    handler for this type
}
            如果在try语句快中的任何代码抛出一个在catch子句中说明的异常类,程序将跳过try中剩余代码,将执行catch子句中的处理器代码。
API(java.lang.Throwable)
Throwable()//构造一新的Throwable对象,这个对象没有详细的描述信息
Throwable(String message)//构造一新的throwable对象,这个对象带有特定的详细描述信息。习惯上,所有派生的异常类都将支持默认的构造器和一个带有详细描述信息的构造器
String getMessage()//获得Throwable对象的详细描述信息
  1. 编译器严格执行throws说明符。如果调用一个抛出已检查异常的方法,就必须对它进行处理,或将他传递出去。通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常传递出去。
  1. 通常用于表示子系统故障的异常类型可能会产生多种解释,例如下面的ServletException,执行servlet的代码可能不想知道发生错误的具体细节原因,只希望知道Servlet是否有故障:
try{
    access the database
}catch(SQLException e){
    Throwable se=new ServletException("database error");
    se.initCause(e);
    throw se;
}
            当捕获到异常时,就可以使用下面这条语句重新得到原始异常:Throwable e=se.getCause();建议使用这种包装技术,这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
  1. 内层的try语句只有一个职责就是关闭输入流,外层的try语句也只有一个职责,就是确保报告出现的错误。这种设计方式不仅清除,而且还有一个功能,就是将会报告finally子句出现的错误。
InputStream in=...;
try{
    try{
        cade thar might throw exceptions
    }finally{
        in.close();
    }
}catch(IOException e){
    show error dialog;
}
  1. 堆栈跟踪是一个方法调用过程的列表,他包含了程序执行过程中方法调用的特定位置。在Java SE1.4以前的版本,可以调节Throwable类的printStackTrace方法访问堆栈跟踪的文本描述信息。现在可以调用getStackTrace方法获得一个StackTraceElement对象的数组,并在程序中对它进行分析。StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时,还含有能够获得类名和方法名的方法。toString方法将产生一个格式化的字符串,其中包含所获得的信息。在Java SE5.0中,增加了一个静态Thread.getAllStackTrace方法,它可以产生所有线程的堆栈跟踪。
API(java.lang.Throwable)
Throwable(Throwable cause)
Throwable(String message,Throwable cause)//用给定的诱饵构造一个Throwable对象
Throwable initCause(Throwable cause)//将对象设置为诱饵,如果这个对象已经被设置为“诱饵”,则抛出一个异常,返回this引用
Throwable getCause()//获得设置为这个对象的诱饵的异常对象,如果没有设置“诱饵”则返回null
StackTraceElement[] getStackTrace()//获得构造这个对象时调用堆栈的跟踪
API(java.lang.Exception)
Exception(Throwable cause)
Exception(String message,Throwable cause)//用给定的诱饵构造一个异常对象
API(java.lang.RuntimeException)
RuntimeException(Throwable cause)
RuntimeException(String message,Throwable cause)//用给定的诱饵构造一个RuntimeException对象
API(java.lang.StackTraceElement)
String getFileName()//返回这个元素运行时对应的源文件名,如果这个信息不存在,则返回null
int getLineNumber()//返回这个元素运行时对应的源文件行数,如果这个信息不存在,则返回-1
String getClassName()//返回这个元素运行时对应的类全名
String getMethodName()//返回这个元素运行时对应的方法名,构造器名为,静态初始化器名是。这里无法区分同名的重载方法
boolean isNativeMethod()//如果这个元素运行时在一个本地方法中,则返回true
String toString()//如果存在,返回一个包含类名、方法名、文件名、行数的格式化字符串。

  1. 断言:断言机制允许测试期间向代码中插入一些检查语句。当代码发布时,这些插入的检测语句会被自动移走。在JavaSE1.4中,Java语句引入了关键字assert。这个关键字有两种形式:assert 条件;和assert 条件:表达式。这两种形式都会对条件进行检测,如果结果false,则会抛出一个AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串。
  2. 在默认情况下,断言被禁用,可以在运行程序时用-enableassertions或-ea选项启动它。java -enableassertion MyApp。启用或禁用断言是类加载器的功能,当断言被禁止时,类加载器将跳过断言代码。系统类没有类加载器,对于这些系统类来说,需要使用-enablesystemassertions/esa开关启用断言。
API(java.lang.ClassLoader)
void setDefaultAssertionstatus(boolean b)//对于通过类加载器加载的所有类来说,如果没有显示地说明类或包的断言状态,就启用或禁用断言。
void setClassAssertionStatus(String className,boolean b)//对于给定的类和他的内部类,启用或禁用断言
void setPackageAssertionStatus(String packageName,boolean b)//对于给定包和其子包中的所有类,启用或禁用断言。
void clearAssertionStatus()//移去所有类和包的显示断言状态设置,并禁用所有通过这个类加载器加载的类的断言。

  1. 日志系统管理着一个名为Logger.global的默认日志记录器,可以用System.out替换它,并通过调用info方法记录日志信息。
  2. 当第一次请求一个具有给定名字的日记记录器时,就会创建这个记录器。Logger myLogger=Logger.getLogger("com.mycompany.app");通常有7个日志记录级别:1.SEVER;2.WARNING;3.INFO;4.CONFIG;5.FINE;6.FINER;7.FINEST在默认情况下,只记录前三个级别,也可以设置其他的级别。looger.setLevel(Lever.FINE)。还可以使用Level.ALL开启所有级别的记录,或者使用Level.OFF关闭所有级别的记录。默认的日志记录将显示包含日志调用的类名和方法名,如同堆栈所显示的那样。但是,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。此时,可以调用logp方法获得调用类和方法的确切位置,这个方法的签名为:void logp(Level l,String className,String methodName,String message)。
  3. 可以使用下面两个方法提供日志记录中包含的异常描述内容
void throwing(String className,String methodName,Throwable t)
void log(Level l,String message,Throwable t)
  1. 可以通过修改配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于:jre/lib/logging.properties。要想使用另一个配置文件,就要将java.util.logging.config.file特性设置为配置文件的存储位置,并用下列命令启动应用程序:java -Djava.util.logging.config.file=configFile MainClass。
  2. 本地化,本地化的应用程序包含资源包,一个程序可以包含多个资源包,一个用于菜单,其他用于日志消息,每个资源包都有一个名字,要想将映射添加到一个资源包中,需要为每个地区创建一个文件。可以将这些文件与应用程序的类文件放在一起,在请求日志记录器时,可以指定一个资源包:Logger logger=Logger.getLogger(loggerName,"com.mycompany.app");然后为日志消息指定资源包的关键字,而不是实际的日志消息字符串:logger.info("readingFile")

  1. 处理器:在默认情况下,日志记录器会把记录发送给ConsoleHandler中,并且由他输到System.err流中。特别是,日志记录器还会将记录发送到父处理器中,而最终的处理器有一个ConsoleHandler。与日志记录器一样,处理器也有日志记录级别,对于一个要被记录的日志记录,他的日志记录级别必须高于日志记录器和处理器的阈值,日志记录器配置文件设置的默认控制台处理器的日志记录级别为:java.util.logging.ConsoleHandler.level=INFO。要想记录FINE级别的日志就必须修改配置文件中的默认日志记录级别和处理器级别与处理器级别。另外还可以绕过配置文件,安装自己的处理器
Logger logger=Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
Handler handler=new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
  1. 要想将日志记录发送到其他地方,就要添加其他的处理器,日志API为此提供了两个很有用的处理器,一个是FIleHandler,他可以收集文件中的记录;另一个是SocketHandler。SocketHandler将记录发送到特定的主机和端口。开启文件循环功能也是一个不错的注意。日志文件以myapp.log.0,myapp.log.1,......这种循环序列的形式出现,只要文件超出了大小限制,最旧的文件就会被删除,其他的文件将重新命名,同时创建一个新文件。

java.util.logging.FileHandler.level
处理器级别
Level.ALL
java.util.ogging.FileHandler.append
控制处理器应该追加到一个已经存在的文件尾部;还是应该为每个运行的程序打开一个新文件
false
java.util.logging.FileHandler.limit
在打开另一个文件之前允许写入一个文件的近似最大字节数
在FileHandler类中为0,默认的日志记录器配置文件中为50000
java.util.logging.FileHandler.pattern
日志文件名的模式
%h/java%u.log
java.util.logging.FileHandler.count
在循环序列中的日志记录数量
1(不循环)
java.util.logging.FileHandler.filter
使用的过滤器类
没有使用过滤器
java.util.logging.FileHandler.encoding
使用的字符编码
平台的编码
java.util.logging.FIleHandler.formatter
记录格式器
java.util.logging.XMLFormatter
日志记录文件模式变量

%h
系统属性user.home的值
%t
系统临时目录
%u
用于解决冲突的唯一性编号
%g
为循环日志记录产生的数值
%%
%字符
  1. 默认情况下,过滤器根据日志记录的级别进行过滤,每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤,另外,可以通过实现Filter接口定义下列方法来自定义过滤器:boolean isLoggable(LogRecord record)。要想将一个过滤器安装到一个日志记录器或处理器,只需要setFilter方法就可以。
  2. 24格式化器:可以自定义格式,这需要扩展Formatter类并覆盖下面这个方法:String format(LogRecord record)
  3. 日志记录说明书:
  • 为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字
  • 默认的日志配置将级别高于或等于INFO级别的所哟消息记录到控制台,用户可以覆盖默认的配置文件。最好在应用程序中安装一个更加适宜的默认配置。下面这段代码可以将所有消息记录到应用程序特定的文件中。可以将其放置到应用程序的main方法中。
if(System.getProperty("java.util.logging.config.class")==null&&System.getProperty("java.util.logging.config.file")==null){
    try{
        Logger.getLogger("").setLevel(Level.ALL);
        final int LOG_ROTATION_COUNT=10;
        Handle handler=new FileHandler("%h/myapp.log",0,LOG_ROTATION);
        Logger.getLogger("").addHandler(handler);
    }catch(IOException e){
        logger.log(Level.SEVERE,"Can't create log file handler",e);
    }
现在可以记录自己想要的内容了

API(java.util.logging.Logger)
Logger getLogger(String loggerName)
Logger getLogger(String loggerName,String boudleName)//获得给定名字的日记记录器。如果这个日志记录器不存在,创建一个日志记录器。
void severe(String message)
void warning(String message)
void info(String message)
void config(String message)
void fine(String message)
void finer(String message)
void finest(String message)//记录一个由给定方法名和给定消息指示级别的日志记录
void entering(String className,String methodName)
void entering(String className,String methodName,Object param)
void entering(String className,String methodName,Object[] param)
void exiting(String className,String methodName)
void exiting(String className,String methodName,Object result)//记录一个描述进入/退出方法的日志记录,其中应该包含给定参数和返回值
void throwing(String className,String methodName,Throwable t)//记录一个描述抛出给定异常对象的日志记录
void log(Level level,String message)
void log(Level level,String message,Object obj)
void log(Level level,String message,Object[] objs)
void log(Level level,String message,Throwable t)//记录一个给定级别和消息的日志记录,其中包括对象或者可抛出对象。要想包括对象,消息中必须包含格式化占位符{0}、{1}等。
void logp(Level level,String className,String methodName,String message)
void logp(Level level,String className,String methodName,String message,Object obj)
void logp(Level level,String className,String methodName,String message,Throwable t)
void logp(Level level,String className,String methodName,String message,Object[] obj)//记录一个给定级别、准确的调用者信息和消息的日志记录,其中可以包括对象或可抛出对象
void logrb(Level level,String className,String methodName,String bundleName,String message)
void logrb(Level level,String className,String methodName,String bundleName,String message,Object obj)
void logrb(Level level,String className,String methodName,String bundleName,String message,Object[] obj)
void logrb(Level level,String className,String methodName,String bundleName,String message,Throwable t)//记录一个给定级别、准确的调用者信息、资源名和消息的日志记录,其中可以包括对象或可抛出对象。
Level getLevel()
void setLevel(Level l)//获得和设置这个日志记录器的级别。
Logger getParent()
void setParent(Loggger l)//获得和设置这个日志记录器的父日志记录器
Handler[] getHandlers()//获得这个日志记录器的所有处理器
void addHandler(Handler h)
void removeHandler(Handler h)//增加或删除这个日志记录器中的一个处理器
boolean getUseParentHandlers()
void setUseParentHandlers(boolean b)//获得和设置“use parent handler"属性,如果这个属性是true,则日志记录器会 将全部的日志记录转发给他的父处理器
Filter getFilter()
void setFilter(Filter f)//获得和设置这个日志记录器的过滤器
API(java.util.logging.Handler)
abstract void publish(LogRecord record)//将日志记录发送到希望的目的地
abstract void flush()//刷新所有已缓冲的数据
abstract void close()//刷新所有已缓冲的数据,并释放所有相关的资源
Filter getFilter()
void setFilter(Filter f)//获得和设置这个处理器的过滤器
Formatter getFormatter()
void setFormatter(Formatter f)//获得和设置这个处理器的格式化器
Level getLevel()
void setLevel(Level l)//获得和设置这个处理器的级别
API(Java.util.logging.ConsoleHandler)
ConsoleHandler()//构造一个新的控制台处理器
API(java.util.logging.FileHandler)
FileHandler(String pattern)
FileHandler(String pattern,boolean append)
FileHandler(String pattern,int limit,int count)
FileHandler(String pattern,int limit,int count,boolean append)//构造一个文件处理器
pattern    构造日志文件名的模式
limit    在打开一个新日志记录文件之前,日志文件可以包含的近似最大字节数
count    循环序列的文件数量
append    新构造的文件处理器对象那个应该追加在一个已存在的日志文件尾部,则为true
API(java.util.logging.LogRecord)
Level getLevel()//获得这个日志记录的记录级别
String getLoggerName()//获得正在记录这个日志记录的日志记录器的名字
ResourceBundle getresourceBundle()
String getresourceBundleName()//获得用于本地化消息的资源包或资源包的名字,如果没有获得,返回null
String getMessage()//获得本地化和格式化之前的原始消息
Object[] getParameters()//获得参数对象。如果没有获得,则返回null
Throwable getThrown()//获得被抛出的对象,如果不存在,则返回null
String getSourceClassName()
String getSourceMethodName()//获得记录这个日志记录的代码区域,这个信息有可能是由日志记录代码提供的,也有可能是自动从运行时堆栈推测出来的,如果日志记录代码提供的值有误,或者运行时代码由于被优化而无法推测出确切的位置,这两个方法的返回值就有可能不准确
long getMillis()//获得创建时间。以毫秒为单位
long getSequenceNumber()//获得这个日志记录的唯一序列序号
int getThreadID()//获得创建这个日志记录的线程的唯一ID,这些ID是由LogRecord类分配的,并且与其他线程的ID无关
API(java.util.logging.Filter)
boolean isLoggable(LogRecord record)//如果给定日志记录需要记录,则返回true
API(java.util.logging.Formatter)
abstract String format(Logrecord record)//返回对日志记录格式化后得到的字符串
String getHead(Handler h)
String getTail(Handler h)//返回应该出现在包含日志记录的文档的开头和结尾的字符串。超类Formatter定义了这些方法,他们只返回空字符串,如果必要的话,可以对他们进行重载
String formatMessage(LogRecord record)//返回经过本地化和格式化后的日志记录的消息内容。
  1. 调试技术:
  • 可以用System.out.println或者Logger.global.info的方法打印或记录任意变量的值
  • 在每一个类中放置一个main方法,这样就可以对每个类进行单元测试
  • 可以了解一下Junit,是一个常见的单元测试框架
  • 日志代理是一个子类的对象,他可以窃取方法调用,并进行日志记录,然后调用超类中的方法
  • 利用Throwable类提供的printStackTrace方法。可以从任何一个异常对象中获得堆栈情况。除了通过try,catch语句捕获异常生成堆栈跟踪,只要在代码任何位置插入Thread.dumpStack()都可以获得堆栈跟踪。
  • 通常堆栈跟踪显示在System.err上,也可以利用printStackTrace方法来将他发送到一个文件中,另外,如果想记录或显示堆栈跟踪,可将其捕获到一个字符串中:
StringWriter out=new StringWriter();
new Throwable().printStackTrace(new PrintWriter(out));
String trace=out.toString();
  • 通常将一个程序的错误信息保存到文件中是很有用的,但是错误信息被发送到System.err,而不是System.out中,因此需要使用类似java MyProg 2>errors.txt来捕获错误流,要想在同一个文件中同时捕获system.err和System.out,使用下面这条指令:java MyProgram >& errors.txt。
  • 非捕获异常的堆栈跟踪出现在System.err中不是很好,意识客户端看到会很迷惑,另外需要的时候也无法实现诊断目的。比较好的方式是将他们记录到一个文件中,可以调用静态的Thread.setDefaultUncayghtExceptionHandler方法改变非捕获异常的处理器:
Thread.setDefaultUncaughtExceptionHandler(
    new Thread.UnCaughtExceptionHandler(){
        public void uncaughtException(Thread t,Throwable e){
            save information in log file;
        };
    }
);
  • 要想观察类的加载过程,用-verbose标志启动Java虚拟机。这种方法有助于诊断类路径引发的问题。
  • 如果看过Swing窗口,并对设计者能够采用某种管理手段将所有的组件排列整齐,就可以监视一下有关信息,按下CTRL+SHIFT+F1,就会按照层次结构打印出所有组件信息。
  • 如果自定义了Swing组件,却不能正确显示,则会发现Swing图形调试器是一个不错的工具。要想对一个组件进行调试,可以调用JComponent类的setDefaultGraphicsOptions方法。下面是他的可选项:
    • DebugGraphics.FLASH_OPTION 绘制每条线段,每个矩形、每个文本之前,用红色闪烁显示
    • DebugGraphics.LOG_OPTIONG  打印每次绘制操作的信息
    • DebugGraphics.BUFFERED_OPTION  显示在显示区域之外执行的缓冲操作
    • DebugGraphics.NONE_OPTION  关闭图形调试
        要使闪烁选项能够工作,就必须禁用双重缓冲,这是Swing为缓解更新窗口时屏幕抖动现象所采取的一种策略。下面是用来开启闪烁选项的代码:

RapaintManager(getRootPane()).setDoubleBufferingEnabled(false);//禁用双缓冲
((JComponent)getContentPane()).setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);

  • Java还有-Xlint选项,编译器就可以对一些普遍容易出现代码问题进行检查。例如:javac -Xlint:fallthrough当switch缺少break时,就会给出报告。下面列出可以使用的选现:
    • -Xlint或-Xlint:all 执行所有检查
    • -Xlint:deprecation 与-deprecation一样,检查所有反对使用的方法
    • -Xlint:fallthrough 检查所有switch语句是否缺少break
    • -Xlint:finally警告finally子句不能正常地执行
    • -Xlint:none不执行任何检查
    • -Xlint:path 检查类路径和源代码路径上的所有目录是否存在
    • -Xlint:serial 警告没有serialVersionUID的串行化类
    • -Xlint:unchecked 对通用类型和原始类型之间的危险转换给予警告
  • Java增加了对Java应用程序进行监控和管理的支持,jDK可以加载一个称为jconsole的图形工具,可以用于显示虚拟机性能的统计结果,找出运行虚拟机的操作系统进程ID,在UNIX/Linux环境下,运行ps实用工具,在Windows环境下,使用任务管理器,然后运行jconsole程序。jconsole processID.
  • 可以使用jmap实用工具获得一个堆的堆放处,其中显示了堆中的每个对象,使用命令如下:jmap -dump:format=b,file=dumpFileName processID;jhat dumpFileName。然后用浏览器进入localhost:7000将会得到一个网络应用程序,借此探查倾到对象时堆的内容。
  • 如果使用-Xprof标志运行java虚拟机,就会运行一个基本的剖析器来跟踪那些代码中经常被调用的方法,剖析信息将发送给System.out。输出结果中还会显示哪些方法是由just-in-time编译器编译的

API(java.awt.GraphicsEnvironment)
static GraphicsEnvironment getLocalGraphicsEnvironment()//返回本地图形环境
GraphicsDevice getDefaultScreenDevice()//返回默认的屏幕设备,使用多台显示器的电脑,每一个屏幕可以有一个图形设备,通过调用getSreenDevices方法可以得到一个保存所有屏幕设备的数组
API(java.awt.Robot)
Robot(GraphicsDevice device)//构造一个能够与给定设备交互的robot对象
void keyPress(int key)
void keyRelease(int key)//模拟按下或释放按键
void mouseMove(int x,int y)//模拟移动鼠标
void mousePress(int eventMask)
void mouseRelease(int eventMask)//模拟按下鼠标键
void delay(int milliseconds)//根据给定毫秒数延迟robot
BufferedImage createScreenCapture(Rectangle rect)//截取屏幕的一部分


你可能感兴趣的:(Chapter11-异常日志断言和调试)