七、异常,断言和日志

一、处理错误

  如果由于出现错误而使得某些操作没有完成, 程序应该:

  • 返回到一种安全状态,并能够让用户执行一些其他的命令;或者
  • 允许用户保存所有操作的结果,并以妥善的方式终止程序

异常分类

  

   Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该 抛出这种类型的对象。 如果出现了这样的内部错误, 除了通告给用户,并尽力使程序安全地 终止之外, 再也无能为力了。这种情况很少出现。:由 程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类 问题导致的异常属于其他异常:

派生于 RuntimeException 的异常包含下面几种情况:

  • •错误的类型转换。
  • •数组访问越界
  •  •访问 null 指针

不是派生于 RuntimeException 的异常包括:

  • •试图在文件尾部后面读取数据。
  • •试图打开一个不存在的文件。
  • •试图根据给定的字符串查找 Class 对象, 而这个字符串表示的类并不存在。

   Java 语 言 规 范 将 派 生 于 Error 类 或 RuntimeException 类的所有异常称为非受查 ( unchecked ) 异常,所有其他的异常称为受查( checked) 异常。这是两个很有用的术语,在 后面还会用到。 编译器将核查是否为所有的受査异常提供了异常处理器。

   RuntimeException 这个名字很容易让人混淆。 实际上,现在讨论的所有错误都发 生在运行时。

声明受查异常

public FileInputStream(String name) throws FileNotFoundException

   这个声明表明这个构造器将根据给定的 String 参数产生一个 FilelnputStream 对象,但也 有可能抛出一个 FileNotFoimdException 异常。如果发生了这种糟糕情况, 构造器将不会初始 化一个新的 FileInputStream 对象, 而是抛出一个 FileNotFoundException 类对象。 如果这个方 法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器, 以便知道如何处理 FileNotFoundException 对象„

 需要记住在遇到下面 4 种 情况时应该抛出异常:

1 ) 调用一个抛出受査异常的方法, 例如, FilelnputStream 构造器。

2 ) 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常(下一节将详细地 介绍 throw 语句)。

3 ) 程序出现错误, 例如,a[-l] =0 会抛出一个 ArraylndexOutOfBoundsException 这样的 非受查异常。

4 ) Java 虚拟机和运行时库出现的内部错误。

如果出现前两种情况之一, 则必须告诉调用这个方法的程序员有可能抛出异常。

  对于那些可能被他人使用的 Java 方法, 应该根据异常规范( exception specification), 在 方法的首部声明这个方法可能抛出的异常。如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常 类。每个异常类之间用逗号隔开。但是, 不需要声明 Java 的内部错误,即从 Error 继承的错误。任何程序代码都具有抛出那些 异常的潜能, 而我们对其没有任何控制能力。 同样,也不应该声明从 RuntimeException 继承的那些非受查异常。

  总之,一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制( Error), 要么就应该避免发生( RuntimeException)。如果方法没有声明所有可能发生的受查异常, 编译器就会发出一个错误消息。

   如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方 法中声明的异常更通用 (也就是说, 子类方法中可以抛出更特定的异常, 或者根本不抛 出任何异常)。特别需要说明的是, 如果超类方法没有抛出任何受查异常, 子类也不能抛 出任何受查异常。

子类覆盖父类方法为什么不能抛出更通用的异常

  如果类中的一个方法声明将会抛出一个异常, 而这个异常是某个特定类的实例时, 则这个方法就有可能抛出一个这个类的异常, 或者这个类的任意一个子类的异常。

  在 Java 中, 没有 throws 说明符的方法将不能抛出任何受查异常。

如何抛出异常

例:

String readData(Scanner in) throws EOFException{
		while() {
			if(!in.next) {
				if(n<len) {
					throw new EOFException();
				}
			}
		}
	}

对于一个已经存在的异常类, 将其抛出非常容易. 在这种情况下:

1 ) 找到一个合适的异常类。

2 ) 创建这个类的一个对象。

3 ) 将对象抛出。

 一旦方法抛出了异常, 这个方法就不可能返回到调用者。也就是说, 不必为返回的默认 值或错误代码担忧。

创建异常类

  我们需要做的只是定义一个派生于 Exception 的类,或者派生于 Exception 子类的类。定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息 的构造器(超类 Throwable 的 toString 方法将会打印出这些详细信息, 这在调试中非常有用)。

class F extends IOException{
	public F() {};
	public F(String gripe) {
		super(gripe);
	}
}

 

二、捕获异常

捕获异常

  如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台 上打印出异常信息, 其中包括异常的类型和堆栈的内容。

  要想捕获一个异常, 必须设置 try/catch语句块。最简单的 try语句块如下所示:

try{
more code
}
catch (ExceptionType e)
{
handlerfor this type
}

如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类, 那么

1 ) 程序将跳过 try语句块的其余代码。

2 ) 程序将执行 catch 子句中的处理器代码。

如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句。

  如果调用了一个抛出受查异常的方法,就必 须对它进行处理, 或者继续传递。

  如果编写一个覆盖超类的方法, 而这个方法又没有抛出异常(如 JComponent 中的 paintComponent ), 那么这个方法就必须捕 获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所 列出的异常类范围。

捕获多个异常

  异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息, 可以试着使用 e.getMessage() 得到详细的错误信息(如果有的话,) 或者使用 e.getClass().getName()得到异常对象的实际类型。

  同一个 catch 子句中可以捕获多个异常类型。捕获多个异常时, 异常变量隐含为 final 变量。例如,不能在以下子句体中为 e 赋 不同的值:

catch (FileNotFoundException | UnknownHostException e) { . . . }

例:

try
{
code that might throw exceptions
}
catch (FileNotFoundException | UnknownHostException e)
{
emergency action for missing files and unknown hosts
}
catch (IOException e)
{
emergency action for all other I/O problems
}

再次抛出异常与异常链

  在 catch 子句中可以抛出一个异常,这样做的目的是改变异常的类型。不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的“ 原因”:

try
{
access the database
}
catch (SQLException e){
Throwable se = new ServletException ("database error");
se.initCause(e);
throw se;
}

当捕获到异常时,就可以使用下面这条语句重新得到原始异常

Throwable e=se.getCause();

   强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

   如果在一个方法中发生了一个受查异常, 而不允许抛出它, 那么包装技术就十分有用。我们可以捕获这个受查异常,并将它包装成一个运行时异常。

  有时你可能只想记录一个异常, 再将它重新抛出, 而不做任何改变:

try
{
access the database
}
catch (Exception e)
{
logger.log(level, message, e);
throw e;
}

  假设 e 在 catch 块中未改变, 将外围方法声明为 throws SQLException 就是合法的。

finally子句

  当代码抛出一个异常时, 就会终止方法中剩余代码的处理,并退出这个方法的执行。如 果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前 必须被回收,那么就会产生资源回收问题。一种解决方案是捕获并重新抛出所有的异常。但 是,这种解决方案比较乏味,这是因为需要在两个地方清除所分配的资源。一个在正常的代 码中;另一个在异常代码中。Java 有一种更好的解决方案,这就是 finally 子句。

  不管是否有异常被捕获,finally 子句中的代码都被执行。在下面的示例中, 程序将在所 有情况下关闭文件。

InputStream in = new FileInputStream(. . .);
try{
I I I
code that might throwexceptions
I I I
}
catch (IOException e)
{
// 3
showerror message
// 4
}
finally
{
// 5
in.closeO;
}
//6

  在上面这段代码中,有下列 3 种情况会执行 finally 子句:

1 ) 代码没有抛出异常。 在这种情况下, 程序首先执行 try 语句块中的全部代码,然后执 行 finally 子句中的代码t 随后, 继续执行 try 语句块之后的第一条语句。也就是说,执行标 注的 1、 2、 5、 6 处。

2 ) 抛出一个在 catch 子句中捕获的异常。在上面的示例中就是 IOException 异常。在这种 情况下,程序将执行 try语句块中的所有代码,直到发生异常为止。此时,将跳过 try语句块中 的剩余代码,转去执行与该异常匹配的 catch 子句中的代码, 最后执行 finally 子句中的代码。 如果 catch 子句没有抛出异常,程序将执行 try 语句块之后的第一条语句。在这里,执行 标注 1、 3、 4、5、 6 处的语句。 如果 catch 子句抛出了一个异常, 异常将被抛回这个方法的调用者。在这里, 执行标注 1、 3、 5 处的语句。

3 ) 代码抛出了一个异常, 但这个异常不是由 catch 子句捕获的。在这种情况下,程序将 执行 try 语句块中的所有语句,直到有异常被抛出为止。此时, 将跳过 try 语句块中的剩余代 码, 然后执行 finally 子句中的语句, 并将异常抛给这个方法的调用者。在这里, 执行标注 1、 5 处的语句。

try 语句可以只有 finally 子句,而没有 catch 子句。

  强烈建议解搞合 try/catch 和 try/finally 语句块。这样可以提高代码的清晰度。例如:

InputStrean in = . . .;
try
{
try
{
code that might throwexceptions
}
finally
{
in.doseO;
}
}
catch (IOException e)
{
show error message
}

  内层的 try语句块只有一个职责, 就是确保关闭输入流。外层的 try 语句块也只有一个职 责, 就是确保报告出现的错误。这种设计方式不仅清楚, 而且还具有一个功能,就是将 会报告 finally 子句中出现的错误。

  当 finally 子句包含 return 语句时, 将会出现一种意想不到的结果„ 假设利用 return 语句从 try语句块中退出。在方法返回前, finally 子句的内容将被执行。如果 finally 子句中 也有一个 return 语句,这个返回值将会覆盖原始的返回值。

  有时候, finally 子句也会带来麻烦。例如, 清理资源的方法也有可能抛出异常。现在,假设在 try 语句块中的代码抛出了一些非 IOException 的异常,这些异常只有这个 方法的调用者才能够给予处理。执行 finally 语句块,并调用 dose 方法。而 close 方法本身也 有可能抛出 IOException 异常。当出现这种情况时, 原始的异常将会丢失,转而抛出 close 方 法的异常。 这会有问题, 因为第一个异常很可能更有意思。如果你想做适当的处理,重新抛出原来 的异常, 代码会变得极其繁琐:

        InputStream in = . . .;
        Exception ex = null;
        try{
            try{
                code that might throw exceptions
            }
            catch (Exception e){
                ex=e;
                throw e;
            }
        }
        finally
        {
            try
            {
                in.close();
            }
            catch (Exception e)
            {
                if (ex = null) throw e;
            }
        }

带资源的try语句

  带资源的 try 语句(try-with-resources) 的最简形式为:

try (Resource res = . . .)
{
work with res
}

 假设资源属于一个实现了 AutoCloseable 接口的类, 另外,还有一个Closeable 接口。这是 AutoCloseable 的子接口, 也包令 •个 close 方法。不过, 这个方法声明为抛出一个IOException.Java SE 7为这种代码模式提供了一 个很有用的快捷方式.try块退出时,会自动调用 res.close()。下面给出一个典型的例子, 这里要读取一个文件 中的所有单词:

try (Scanner in = new Scanner(new FileInputStream(/usr/share/dict/words")), "UTF-8")
{
while (in.hasNext())
System.out.println(in.next());
}

这个块正常退出时, 或者存在一个异常时, 都会调用 in.close() 方法, 就好像使用了 finally块一样。 还可以指定多个资源: 例如:

try (Scanner in = new Scanner(new FileInputStream('/usr/share/dict/words"). "UTF-8"):
PrintWriter out = new PrintWriter("out.txt"))
{
while (in.hasNext())
out.println(in.next().toUpperCaseO);
}

  上一节已经看到,如果 try块抛出一个异常, 而且 close方法也抛出一个异常,这就会带 来一个难题.,带资源的 try语句可以很好地处理这种情况。原来的异常会重新抛出,而 close 方法抛出的异常会“ 被抑制% 这些异常将自动捕获,并由 addSuppressed方法增加到原来的 异常。如果对这些异常感兴趣, 可以调用 getSuppressed方法,它会得到从 close 方法抛出并 被抑制的异常列表。

 只要需要关闭资源, 就要尽可能使用带资源的 try 语句。 带资源的try语句自身也可以有catch 子句和一个finally 子句。 这些子句会在 关闭资源之后执行。 不过在实际中, 一个try 语句中加入这么多内容可能不是一个好 主意。

分析堆栈轨迹元素

  堆栈轨迹( stack trace ) 是一个方法调用过程的列表, 它包含了程序执行过程中方法调用 的特定位置前面已经看到过这种列表, 当 Java 程序正常终止, 而没有捕获异常时, 这个列表就会显示出来。

  可以调用 Throwable 类的 printStackTrace 方法访问堆栈轨迹的文本描述信息.

Throwable t = new ThrowableO;
StringWriter out = new StringWri ter() ;
t.printStackTrace(new PrintWriter(out));
String description = out.toString();

一种更灵活的方法是使用 getStackTrace 方法, 它会得到 StackTraceElement 对象的一个 数组, 可以在你的程序中分析这个对象数组。

Throwable t = new Throwable() ;
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement frame : frames){
analyze frame
}

静态的 Thread.getAllStackTrace 方法, 它可以产生所有线程的堆栈轨迹 .

Map<Thread, StackTraceElement[]> map = Thread.getAl1StackTraces();
for (Thread t : map. keySet ())
{
StackTraceElement[] frames = map.get(t);
analyze frames
}

例子:

package stackTrace;

import java.util.*;

/**
 * A program that displays a trace feature of a recursive method call.
 * @version 1.01 2004-05-10
 * @author Cay Horstmann
 */
public class StackTraceTest
{
   /**
    * Computes the factorial of a number
    * @param n a non-negative integer
    * @return n! = 1 * 2 * . . . * n
    */
   public static int factorial(int n)
   {
      System.out.println("factorial(" + n + "):");
      Throwable t = new Throwable();
      StackTraceElement[] frames = t.getStackTrace();
      for (StackTraceElement f : frames)
         System.out.println(f);
      int r;
      if (n <= 1) r = 1;
      else r = n * factorial(n - 1);
      System.out.println("return " + r);
      return r;
   }

   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);
      System.out.print("Enter n: ");
      int n = in.nextInt();
      factorial(n);
   }
}

三、使用异常机制的技巧

  1. 异常处理不能代替简单的测试

  2. 不要过分地细化异常

  3. 利用异常层次结构

  4. 不要压制异常

  5. 在检测错误时,“ 苛刻 ” 要比放任更好

  6. 不要羞于传递异常

四、使用断言

  设确信某个属性符合要求, 并且代码的执行依赖于这个属性。断言机制允许在测试期间向代码中插入一些检査语句。当代码发布时,这些插人的检测 语句将会被自动地移走。

   Java 语言引人了关键字 assert。这个关键字有两种形式:

  assert 条件;和

  assert 条件:表达式;

  这两种形式都会对条件进行检测, 如果结果为 false, 则抛出一个 AssertionError 异常。 在第二种形式中,表达式将被传人 AssertionError 的构造器, 并转换成一个消息字符串。“ 表达式” 部分的唯一目的是产生一个消息字符串。

启用或禁用断言

1.Run -> Run Configurations -> Arguments页签 -> VM arguments文本框中加上断言开启的标志:-enableassertions 或者-ea 就可以了

  

使用断言完成参数检查

在 Java 语言中, 给出了 3 种处理系统错误的机制:

•抛出一个异常

•日志

•使用断言

什么时候应该选择使用断言呢? 请记住下面几点:

•断言失败是致命的、 不可恢复的错误。

•断言检查只用于开发和测阶段(这种做法有时候被戏称为“ 在靠近海岸时穿上救生衣, 但在海中央时就把救生衣抛掉吧”)。

  因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作 为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。

  在方法 的开头使用断言: assert a != null; 计算机科学家将这种约定称为前置条件( Precondition)。事实上,由于可以使用断言,当方法被非法调用时,将会出现难以预料的结果。有时候会拋出一个断言错误, 有时候会产生一个 null 指 针异常, 这完全取决于类加载器的配置。



import org.junit.Before;
import org.junit.Test;
/**
 *作者 : iechenyb<br>
 *类描述: 说点啥<br>
 *创建时间: 2017年7月19日
 */
public class StandardUnit {
    @Before//在 VM arguments 文本框中输入:-ea注意中间没有空格,如果输入-da表示禁止断言。
    public void checkIsOpenEclipseAssert(){
        boolean isOpen = false;
        assert isOpen=true; //如果开启了断言,会将isOpen的值改为true
        if(!isOpen)
            System.out.println("是否开启断言"+isOpen);//打印是否开启了断言
    }
    @Test
    public void test(){
        int i=0;int j=1;
        boolean isOk = 1>2;
        assert("")!=null;
        assert("")=="";
        assert !(6==5);//其后的sysout可以打印断言正常
        System.out.println("如果断言正常,我就被打印.");
        System.out.println("如果断言正常,我就被打印!");
        System.out.println("如果断言正常,我就被打印.");
        System.out.println("如果断言正常,我就被打印!");
        //如果冒号前为false,则抛出AssertionError ,错误信息内容为冒号后面的内容
        assert isOk : "程序错误"+isOk;
        assert(i)==j;
        assert(j)==i;
        assert isOk;
        assert true;
        assert false;
        assert i==1;
        assert 1==i;
        assert i==j;
        assert(null)!=null;
    }
    public boolean able(){
        return true;
    }
}

五、记录日志

  Log API 的优点:

可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这 个操作也很容易。

  • 可以很简单地禁止日志记录的输出, 因此,将这些日志代码留在程序中的开销很小。
  • 日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
  • 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准 丢弃那些无用的记录项。
  • 日志记录可以采用不同的方式格式化,例如,纯文本或 XML。
  • 应用程序可以使用多个日志记录器, 它们使用类似包名的这种具有层次结构的名字, 例如, com.mycompany.myapp.
  • 在默认情况下,日志系统的配置由配置文件控制。如果需要的话, 应用程序可以替换 这个配置。

  基本曰志

  要生成简单的日志记录,可以使用全局日志记录器(global logger) 并调用其 info 方法:

       Logger.getGlobal().info("File->Open menu item selected");

 

但是, 如果在适当的地方(如 main 开始)调用

Logger.getGlobal().setLevel(Level.OFF);

将会取消所有的日志。

高级日志

  在一 个专业的应用程序中,不要将所有的日志都记录到一个全局日志记录器中,而是可以自定义 日志记录器。 可以调用 getLogger 方法创建或获取记录器: 

private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp"):

提示:未被任何变量引用的日志记录器可能会被垃圾回收。 为了防止这种情况发生,要 像上面的例子中一样, 用一个静态变量存储日志记录器的一个引用。

  与包名类似,日志记录器名也具有层次结构。事实上, 与包名相比,日志记录器的层次 性更强。例如, 如果对 com.mycompany 日志记录器设置了日志级别, 它的子记录器也会继承这个级别 。

通常, 有以下 7 个日志记录器级别:

• SEVERE

• WARNING

• INFO

• CONFIG

• FINE

• FINER

• FINEST

在默认情况下,只记录前三个级别。 也可以设置其他的级別。例如,

logger,setLevel (Level .FINE); 

  默认的日志配置记录了 INFO 或更高级别的所有记录, 因此, 应该使用 CONFIG、 FINE, FINER 和 FINEST级别来记录那些有助于诊断,但对于程序员又没有太大意义的 调试信息。

  现在, FINE 和更高级别的记录都可以记录下来。 另外, 还可以使用 Level.ALL 开启所有级别的记录, 或者使用 Level.OFF 关闭所有级别 的记录。 对于所有的级别有下面几种记录方法:

logger.warning(message):

logger.fine(message) ;

logger.severe();

同时, 还可以使用 log 方法指定级别, 例如:

logger.log(Level .FINE, message);

   如果将记录级别设计为 INFO 或者更低, 则需要修改日志处理器的配置。 默认的 日志处理器不会处理低于INFO 级别的信息。

  默认的日志记录将显示包含日志调用的类名和方法名, 如同堆栈所显示的那样。但是, 如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。此时,可以调用 logp 方法获 得调用类和方法的确切位置, 这个方法的签名为:

void logp(Level 1, String className, String methodName, String message).
//例
 Logger.getGlobal().logp(Level.INFO,"Thre","test","open");

其实就是手动把类名和方法名加到log里,愚蠢。

下面有一些用来跟踪执行流的方法:

void entering(String dassName , String methodName)

void entering(String className , String methodName , Object param)

void entering(String className , String methodName , Object[] params)

void exiting(String className , String methodName)

void exiting(String className , String methodName , Object result)

例如:

int read(String file, String pattern)
{
logger.entering("com.mycompany.mylib.Reader", "read",
new Object[] { file, pattern });
logger.exiting("com.mycompany.mylib. Reader", "read", count):
return count ;
}

这些调用将生成 FINER 级别和以字符串 ENTRY 和 RETURN 开始的日志记录。

这两个函数的底层实现,就是调用了logp

    public void entering(String sourceClass, String sourceMethod) {
        this.logp(Level.FINER, sourceClass, sourceMethod, "ENTRY");
    }

    public void entering(String sourceClass, String sourceMethod, Object param1) {
        this.logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", param1);
    }
    public void exiting(String sourceClass, String sourceMethod) {
        this.logp(Level.FINER, sourceClass, sourceMethod, "RETURN");
    }

    public void exiting(String sourceClass, String sourceMethod, Object result) {
        this.logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result);
    }

可以使用下面两个方法提供日志记录 中包含的异常描述内容。

void throwing(String className , String methodName , Throwable t)

void log(Level 1 , String message , Throwable t)

典型的用法是:

if (…)
{
IOException exception = new IOException(". . .");
logger.throwing("com.mycompany.mylib.Reader", "read", exception) ;
throw exception;
}
还有
try
{…}
catch (IOException e)
{
Logger.getLogger("com.mycompany.myapp").log(Level .WARNING , "Reading image", e);
}

调用 throwing 可以记录一条 FINER 级别的记录和一条以 THROW 开始的信息。

 

不知道为什么打印不出来信息!!!

 

修改日志管理器配置

  可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于: jre/lib/1ogging.properties(不同情况不一样,搜一下能搜到)。

  要想修改默认的日志记录级别, 就需要编辑配置文件,并修改以下命令行

.level=INFO

可以通过添加以下内容来指定自己的日志记录级别

com.mycompany_myapp.level=FINE

也就是说,在日志记录器名后面添加后缀 .level。 在稍后可以看到,日志记录并不将消息发送到控制台上,这是处理器的任务。另外,处 理器也有级别。要想在控制台上看到 FINE级别的消息, 就需要进行下列设置

java.util.logging.ConsoleHandler.level=FINE

 要想使用另一个配置文件, 就要将 java.utiUogging.config.file 特性设置为配置文件的存 储位置, 并用下列命令启动应用程序:

java -Djava.util.logging.config.file-configFileMainClass 

 日志管理器在 VM 启动过程中初始化, 这在 main 执行之前完成。 如果在 main 中System.setProperty("java.util.logging.config.file",file), 也会调用LogManager. readConfiguration() 来重新初始化曰志管理器。

  在曰志管理器配置的属性设置不是系统属性, 因此, 用 -Dcom.mycompany.myapp. level= FINE 启动应用程序不会对日志记录器产生任何影响 。

本地化

我理解没什么用,竟然还要做关键字映射,狗屁

处理器

  在默认情况下日志记录器将记录发送到 ConsoleHandler中, 并由它输出到 System.err 流中。特别是,日志记录器还会将记录发送到父处理器中,而最终的处理器(命名为“ ”)有 一个 ConsoleHandler。

  与日志记录器一样,处理器也有日志记录级别。对于一个要被记录的日志记录,它的日 志记录级别必须高于日志记录器和处理器的阈值。日志管理器配置文件设置的默认控制台处 理器的日志记录级别为

  java.util.1ogging.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);

 在默认情况下, 日志记录器将记录发送到自己的处理器和父处理器。我们的日志记录 器是原始日志记录器(命名为“ ”)的子类, 而原始日志记录器将会把所有等于或高于 INFO.级別的记录发送到控制台。然而, 我们并不想两次看到这些记录。鉴于这个原因,应该将 useParentHandlers 性设置为属 false。

  要想将日志记录发送到其他地方, 就要添加其他的处理器。日志 AP丨为此提供了两个很 有用的处理器, 一个是 FileHandler ; 另 "h是 SocketHandler。SocketHandler 将记录发送到 特定的主机和端口 ,: 而更令人感兴趣的是 FileHandler, 它可以收集文件中的记录

        Handler handler = new FileHandler();
        handler.setLevel(Level.FINE);
        logger.addHandler(handler);

这些记录被发送到用户主目录的 javan.log 文件中, n 是文件名的唯一编号。 如果用户系 统没有主目录(例如, 在 C://用户//wangwenxuan), 文件就存储在 C:\Window 这样的默认位置上。 在默认情况下, 记录被格式化为 XML。下面是一个典型的日志记录的形式:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2019-11-20T01:34:23.358904600Z</date>
  <millis>1574213663358</millis>
  <nanos>904600</nanos>
  <sequence>3</sequence>
  <logger>com.mycompany.myapp</logger>
  <level>INFO</level>
  <class>Three</class>
  <method>main</method>
  <thread>1</thread>
  <message>File-&gt;Open menu item selected</message>
</record>

可以通过设置日志管理器配置文件中的不同参数(请参看表 7-1 ), 或者利用其他的构造 器(请参看本节后面给出的 APf 注释)来修改文件处理器的默认行为。

也冇可能不想使用默认的日志记录文件名, 因此, 应该使用另一种模式, 例如, %h/ myapp.log

 

如果多个应用程序(或者同一个应用程序的多个副本)使用同一个口志文件, 就应该开 启 append 标志。另外, 应该在文件名模式中使用%u, 以便每个应用程序创建日志的唯一 副本。 开启文件循环功能也是一个不错的主意。日志文件以 myapp.log.0, myapp.log.1 , myapp. log.2, 这种循环序列的形式出现3 只要文件超出了大小限制, 最旧的文件就会被删除, 其他 的文件将重新命名, 同时创建一个新文件, 其编号为0。

还可以通过扩展 Handler 类或 StreamHandler 类自定义处理器。在本节结尾的示例程序中 就定义了这样一个处理器3 这个处理器将在窗口中显示日志记录(如图 7-2 所示这个处理器扩展于 StreamHandler 类, 并安装了一个流。这个流的 write 方法将流显示输 出到文本框中。

使用这种方式只存在一个问题, 这就是处理器会缓存记录, 并且只有在缓存满的吋候才将 它们写人流中, 因此, 需要覆盖 publish方法, 以便在处理器获得每个记录之后刷新缓冲区。

class WindowHandler extends StreamHandler{
    public WindowHandler(){
        final JTextArea output = new JTextArea();
        setOutputStream(new OutputStream() {
            @Override
            public void write(int i) throws IOException {

            }
            public void write(byte[]b,int off,int len){
                output.append(new String(b,off,len));
            }
        });
    }
    public void publish(LogRecord record){
        super.publish(record);
        flush();
    }
}

 

过滤器

 在默认情况下, 过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以 有一个可选的过滤器来完成附加的过滤。另外,可以通过实现 Filter接口并定义下列方法来自定义过滤器。

 

boolean isLoggab1e(LogRecord record) 

  在这个方法中,可以利用自己喜欢的标准,对日志记录进行分析,返回 true 表示这些记 录应该包含在日志中。例如,某个过滤器可能只对 entering方法和exiting 方法产生的消息 感兴趣,这个过滤器可以调用record.getMessage()方法, 并査看这个消息是否用 ENTRY 或 RETURN 开头。 要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用 setFilter方法就可以 了。注意,同一时刻最多只能有一个过滤器。

class TestFilter implements Filter{

    @Override
    public boolean isLoggable(LogRecord logRecord) {
        return false;
    }
}

public class Three {
    static Logger logger = Logger.getLogger("abc");

    public void t() {

    }

    public void test() throws Ex {
        logger.throwing("t", "s", new Ex("good"));
        throw new Ex("good");
    }

    public static void main(String[] args) throws IOException {
        int i = 0;
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable e) {
                // save information in log file

            }
        });
        logger.setLevel(Level.ALL);
        try {
            new Three().test();
        } catch (
                Exception e) {
            StringWriter out = new StringWriter();
            e.printStackTrace(new PrintWriter(out));
            String description = out.toString();
            logger.info(description);
            logger.log(Level.INFO, "A", e);
        }

        Logger logger = Logger.getLogger("com.mycompany.myapp");
        logger.setLevel(Level.FINE);
        logger.setUseParentHandlers(false);
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.FINE);
        logger.addHandler(handler);
        logger.setFilter(new TestFilter());


        logger.info("File->Open menu item selected");
        logger.logp(Level.INFO, "Three", "test", "open");
        logger.logp(Level.INFO, "Three", "test", "open");
        logger.entering("Three", "test");
        logger.exiting("t", "s");
    }
}

格式化器

ConsoleHandler类和 FileHandler类可以生成文本和 XML 格式的日志记录。但是, 也可 以自定义格式。这需要扩展 Formatter类并覆盖下面这个方法:

String format(LogRecord record)

可以根据自己的愿望对记录中的信息进行格式化,并返冋结果字符串。在 format方法 中, 有可能会调用下面这个方法:

String formatMessage(LogRecord record) 

这个方法对记录中的部分消息进行格式化、 参数替换和本地化应用操作。 很多文件格式(如 XML) 需要在已格式化的记录的前后加上一个头部和尾部在这个例 子中,要覆盖下面两个方法:

String getHead(Handler h) 
String getTail(Handler h) 

最后,调用 setFormatter方法将格式化器安装到处理器中

class MyFormatter extends Formatter {

    @Override
    public String format(LogRecord record) {
        return record.getThreadID()+"::"+record.getSourceClassName()+"::"
                +record.getSourceMethodName()+"::"
                +new Date(record.getMillis())+"::"
                +record.getMessage()+"\n";
    }

}
        Logger logger = Logger.getLogger("com.mycompany.myapp");
        logger.setLevel(Level.FINE);
        logger.setUseParentHandlers(false);
        ConsoleHandler handler = new ConsoleHandler();
        handler.setFormatter(new MyFormatter());
        handler.setLevel(Level.FINE);
        logger.addHandler(handler);

 

日志记录说明

package logging;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.*;
import javax.swing.*;

/**
 * A modification of the image viewer program that logs various events.
 * @version 1.03 2015-08-20
 * @author Cay Horstmann
 */
public class LoggingImageViewer
{
   public static void main(String[] args)
   {
      if (System.getProperty("java.util.logging.config.class") == null
            && System.getProperty("java.util.logging.config.file") == null)
      {
         try
         {
            Logger.getLogger("com.horstmann.corejava").setLevel(Level.ALL);
            final int LOG_ROTATION_COUNT = 10;
            Handler handler = new FileHandler("%h/LoggingImageViewer.log", 0, LOG_ROTATION_COUNT);
            Logger.getLogger("com.horstmann.corejava").addHandler(handler);
         }
         catch (IOException e)
         {
            Logger.getLogger("com.horstmann.corejava").log(Level.SEVERE,
                  "Can't create log file handler", e);
         }
      }

      EventQueue.invokeLater(() ->
            {
               Handler windowHandler = new WindowHandler();
               windowHandler.setLevel(Level.ALL);
               Logger.getLogger("com.horstmann.corejava").addHandler(windowHandler);

               JFrame frame = new ImageViewerFrame();
               frame.setTitle("LoggingImageViewer");
               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

               Logger.getLogger("com.horstmann.corejava").fine("Showing frame");
               frame.setVisible(true);
            });
   }
}

/**
 * The frame that shows the image.
 */
class ImageViewerFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 400;   

   private JLabel label;
   private static Logger logger = Logger.getLogger("com.horstmann.corejava");

   public ImageViewerFrame()
   {
      logger.entering("ImageViewerFrame", "<init>");      
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      // set up menu bar
      JMenuBar menuBar = new JMenuBar();
      setJMenuBar(menuBar);

      JMenu menu = new JMenu("File");
      menuBar.add(menu);

      JMenuItem openItem = new JMenuItem("Open");
      menu.add(openItem);
      openItem.addActionListener(new FileOpenListener());

      JMenuItem exitItem = new JMenuItem("Exit");
      menu.add(exitItem);
      exitItem.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               logger.fine("Exiting.");
               System.exit(0);
            }
         });

      // use a label to display the images
      label = new JLabel();
      add(label);
      logger.exiting("ImageViewerFrame", "<init>");
   }

   private class FileOpenListener implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", event);

         // set up file chooser
         JFileChooser chooser = new JFileChooser();
         chooser.setCurrentDirectory(new File("."));

         // accept all files ending with .gif
         chooser.setFileFilter(new javax.swing.filechooser.FileFilter()
            {
               public boolean accept(File f)
               {
                  return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory();
               }

               public String getDescription()
               {
                  return "GIF Images";
               }
            });

         // show file chooser dialog
         int r = chooser.showOpenDialog(ImageViewerFrame.this);

         // if image file accepted, set it as icon of the label
         if (r == JFileChooser.APPROVE_OPTION)
         {
            String name = chooser.getSelectedFile().getPath();
            logger.log(Level.FINE, "Reading file {0}", name);
            label.setIcon(new ImageIcon(name));
         }
         else logger.fine("File open dialog canceled.");
         logger.exiting("ImageViewerFrame.FileOpenListener", "actionPerformed");
      }
   }
}

/**
 * A handler for displaying log records in a window.
 */
class WindowHandler extends StreamHandler
{
   private JFrame frame;

   public WindowHandler()
   {
      frame = new JFrame();
      final JTextArea output = new JTextArea();
      output.setEditable(false);
      frame.setSize(200, 200);
      frame.add(new JScrollPane(output));
      frame.setFocusableWindowState(false);
      frame.setVisible(true);
      setOutputStream(new OutputStream()
         {
            public void write(int b)
            {
            } // not called

            public void write(byte[] b, int off, int len)
            {
               output.append(new String(b, off, len));
            }
         });
   }

   public void publish(LogRecord record)
   {
      if (!frame.isVisible()) return;
      super.publish(record);
      flush();
   }
}

六、调试技巧

1 ) 可以打印或记录任意变量的值

2)一个不太为人所知但却非常有效的技巧是在每一个类中放置一个单独的 main方法。 这样就可以对每一个类进行单元测试。

3 ) 日志代理( logging proxy) 是一个子类的对象, 它可以截获方法调用, 并进行日志记 录,然后调用超类中的方法。

        Random generator = new Random() {
            public double nextDouble() {
                double result = super.nextDouble();
                Logger.getGlobal().info("nextDouble: " + result);
                return result;
            }
        };
        generator.nextDouble();

 

4 ) 利 用 Throwable 类提供的 printStackTrace 方法,可以从任何一个异常对象中获得堆栈 情况。

        StringWriter out = new StringWriter(); 
        new Throwable().printStackTrace(new PrintWriter(out)); 
        String description = out.toString();

  不一定要通过捕获异常来生成堆栈轨迹。只要在代码的任何位置插入下面这条语句就可 以获得堆栈轨迹: Thread.dumpStack():

5 ) —般来说, 堆栈轨迹显示在 System.err 上。也可以利用 printStackTrace(PrintWriter s) 方法将它发送到一个文件中。

6 ) 要想观察类的加载过程, 可以用 -verbose 标志启动 Java 虚拟机。

7 ) -Xlint 选项告诉编译器对一些普遍容易出现的代码问题进行检査。例如, 如果使用 下面这条命令编译: javac -Xlint:fall through.

当 switch语句中缺少 break语句时, 编译器就会给出报告(术语“ lint” 最初用来描述一种定 位 C 程序中潜在问题的工具,现在通常用于描述查找可疑但不违背语法规则的代码问题的工具。 )

 

8)

9)让非捕获异常的堆栈轨迹出现在 System.err中并不是一个很理想的方法。如果在客户 端偶然看到这些消息,则会感到迷惑,并且在需要的时候也无法实现诊断目的。比较好的方 式是将这些内容记录到一个文件中。可以调用静态的 Thread.setDefaultUncaughtExceptionHan dler方法改变非捕获异常的处理器:

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable e) {
               // save information in log file

            }
        });

10)

   java 虚拟机增加了对 Java 应用程序进行监控(monitoring) 和管理(management) 的 支持。它允许利用虚拟机中的代理装置跟踪内存消耗、 线程使用、类加载等情况。这个功能 对于像应用程序服务器这样大型的、长时间运行的 Java 程序来说特别重要。下面是一个能够 展示这种功能的例子:JDK 加载了一个称为jconsole 的图形工具,可以用于显示虚拟机性能 的统计结果, 如图 7-3 所示。找出运行虚拟机的操作系统进程的 ID。在 UNIX/Linux 环境下, 运行 ps实用工具, 在 Windows 环境下,使用任务管理器。然后运行jconsole 程序: jconsole processID

11)

 

.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值