Java核心技术· 卷一(11版)笔记(第7-9章)

第七章 异常、断言和日志

Java中的异常、断言和日志是常用的调试和错误处理工具。

异常是在代码执行过程中发生不可预测的错误时,程序会抛出一个异常来中断当前的执行流程,以防止更严重的错误发生。Java中的异常分为可检查异常(checked exception)和运行时异常(runtime exception)。可检查异常需要在方法的声明中被显式地声明或捕获,而运行时异常则不需要。常见的Java异常类包括:NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException等。

断言可以用来在代码中插入检查点,确保代码满足预期的条件。Java中的断言关键字是assert,可以在代码中使用assert语句来检查程序的某个条件是否满足,如果条件不满足,程序会抛出AssertionError异常。

日志可以帮助程序员追踪程序的执行流程和调试信息。Java中的日志框架包括java.util.logging、log4j、slf4j等。在编写Java程序时,程序员可以使用日志输出相关信息,比如程序的运行状态、错误信息、调试信息等。同时,可以通过设置日志级别来控制日志输出的详细程度。常用的日志级别包括:DEBUG、INFO、WARN、ERROR、FATAL等。

7.1 处理错误

当Java程序发生错误时,需要通过以下步骤进行处理:

  1. 检查错误信息:通过控制台输出或日志文件查看错误信息,获取错误的具体原因和位置。

  2. 分析错误原因:根据错误信息分析错误原因,确定错误发生的难点和可行的解决方案。

  3. 解决错误问题:根据错误分析确定的解决方案进行修复,例如修改代码逻辑、引入第三方库、降低代码复杂度等。

  4. 测试修复代码:对修复的代码进行测试,确保错误得到解决并且不会影响程序的正常运行。

  5. 预防错误:对错误进行分类和记录,预防同类错误的再次出现,并且对程序进行改进,减少错误出现的可能性。

总之,Java程序的错误处理需要耐心、细致和专业的技能,希望以上步骤对您有所帮助。

7.1.1 异常分类

Java异常可以分为三类:

  1. 受检异常(Checked Exception):这些异常在编译期就已经发现了,需要在代码中显式地进行处理。比如IOExceptionSQLException。如果不对这些异常进行处理,代码将无法通过编译。

  2. 运行时异常(Runtime Exception):这些异常是在代码运行期间才可能发生的,比如NullPointerExceptionArrayIndexOutOfBoundsException。这些异常不需要在代码中处理,但是如果不进行处理,程序会崩溃。

  3. 错误(Error):这些异常通常是由于系统资源不足或系统出错导致的,比如OutOfMemoryErrorStackOverflowError。这些异常通常无法恢复,需要对代码进行修改,以避免出现这些异常。

7.1.2 声明检查型异常

Java中声明检查型异常的方式是在方法签名中使用关键字throws,例如:

public void readFile(String fileName) throws IOException {
   
    // code for reading file
}

在这个例子中,方法声明了一个IOException异常可能会被抛出,即方法执行过程中可能会出现读取文件时的IO异常。调用该方法时,调用者需要使用try-catch或者继续向上抛出该异常。

public void readFile(String fileName) {
   
    try {
   
        // code for reading file
    } catch(IOException e) {
   
        // handle the exception, for example:
        System.err.println("Unable to read file: " + fileName);
        e.printStackTrace();
    }
}

声明检查型异常的作用是让调用者知道该方法可能会抛出哪些异常,从而让调用者能够对这些异常进行处理。这种方式通常用于处理一些需要外部资源、网络连接或者文件操作等的方法。

7.1.3 如何抛出异常

在Java中,可以使用throw关键字来手动抛出异常。

  • 首先,在方法中使用throw语句抛出一个异常对象,例如:
public void divide(int dividend, int divisor) throws ArithmeticException {
   
    if (divisor == 0) {
   
        throw new ArithmeticException("Divisor cannot be zero");
    }
    int result = dividend / divisor;
    System.out.println("Result: " + result);
}

在这个例子中,当除数为零时,会手动抛出一个ArithmeticException异常,异常信息为“Divisor cannot be zero”。

  • 要捕获这个抛出的异常,可以使用try-catch语句块,例如:
public static void main(String[] args) {
   
    try {
   
        divide(10, 0);
    } catch (ArithmeticException e) {
   
        System.out.println(e.getMessage());
    }
}

在这个例子中,当调用divide方法时,try块中的代码会被执行。如果抛出了ArithmeticException异常,catch块中的代码会被执行,打印出异常信息。

注意,在方法声明中要加上throws关键字,明确指出方法可能抛出的异常类型。这样做可以让代码的调用者知道需要处理哪些异常。如果不加throws关键字,编译器会报错。

7.1.4 创建异常类

可以使用以下代码创建一个自定义异常类:

public class MyException extends Exception {
    public MyException() {
        super();
    }
    
    public MyException(String message) {
        super(message);
    }
}

这个自定义异常类继承了 Java 的 Exception 类,并提供了两个构造方法,其中一个带有一个字符串参数,用于在创建异常时传递错误信息。通过这个类,可以在代码中抛出自定义异常,并在 catch 块中处理它们。例如:

try {
    if (someCondition) {
        throw new MyException("Something wrong happened");
    }
} catch (MyException e) {
    System.out.println("Caught MyException: " + e.getMessage());
}

7.2 捕获异常

Java中的异常是用来表示程序出现问题的一种机制。当程序运行出现异常时,如果不进行处理,可能会导致程序崩溃或者出现不可预知的错误,给用户带来负面的体验。因此,Java中要求开发者必须对可能抛出的异常进行捕获和处理,以确保程序的健壮性和稳定性。

以下是一些Java中常见的需要捕获异常的情况:

  1. 读取用户输入时,用户可能会输入不合法的数据,例如,要求输入整数,但用户输入了字符串,这时需要捕获输入数据的异常。

  2. 访问网络资源时,网络可能会出现异常,例如,连接断开、服务器宕机等,这时需要捕获网络异常。

  3. 访问文件时,文件可能会不存在、无法读取或无法写入,这时需要捕获文件相关的异常。

  4. 当程序运行出现错误或者逻辑错误时,需要捕获相应的异常来进行处理。

总的来说,捕获异常可以增加程序的健壮性和可靠性,避免程序因为未处理的异常而崩溃。同时,异常的处理也可以帮助我们发现程序中的错误,并进行修复,提高代码质量和可维护性。

7.2.1 捕获异常

在Java中,可以使用 try-catch 语句来捕获和处理异常。使用 try 语句块来包含可能会出现异常的代码,如果发生任何异常,就会跳转到 catch 语句块中执行代码来处理异常。

下面是一个简单的例子:

public class Example {
   
    public static void main(String[] args) {
   
        int[] arr = {
   1, 2, 3};
        try {
   
            System.out.println(arr[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
   
            System.out.println("Error: Array index out of bounds");
        }
    }
}

在上面的例子中,我们尝试访问数组中的第四个元素,但是这个数组只有三个元素,所以会抛出 ArrayIndexOutOfBoundsException 异常。我们使用 try-catch 语句来捕获这个异常,并在 catch 块中输出错误消息。

当使用多个 catch 语句处理不同类型的异常时,应该首先捕获子类异常,然后捕获父类异常。否则,如果先捕获了父类异常,子类异常在捕获时就会被父类异常所捕获,从而无法处理。

public class Example {
   
    public static void main(String[] args) {
   
        try {
   
            int[] arr = {
   1, 2, 3};
            System.out.println(arr[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
   
            System.out.println("Error: Array index out of bounds");
        } catch (Exception e) {
   
            System.out.println("Error: " + e.getMessage());
        }
    }
}

在上面的代码中,我们首先捕获了 ArrayIndexOutOfBoundsException 异常,然后捕获了 Exception 异常。如果抛出其他类型的异常,就会被 Exception 块所捕获。

7.2.2 捕获多个异常

在Java中,我们可以使用多个catch块来捕获不同类型的异常。以下是一种捕获多个异常的方法:

try {
   
    // Some code that may throw exceptions
} catch (ExceptionType1 e1) {
   
    // Exception handling code for ExceptionType1
} catch (ExceptionType2 e2) {
   
    // Exception handling code for ExceptionType2
} catch (ExceptionType3 e3) {
   
    // Exception handling code for ExceptionType3
} catch (Exception e) {
   
    // Exception handling code for all other exceptions
}

在这个例子中,我们使用了四个不同的catch块来分别处理不同类型的异常。最后一个catch块是捕获所有其他未处理的异常的通用块。请注意,catch块的顺序很重要,因为在异常被捕获之前,从上到下的第一个匹配的catch块将被执行。

7.2.3 再次抛出异常和异常链

Java中的异常处理机制中,我们可以通过再次抛出异常来将异常传递给上层调用者。这可以通过使用throw语句来实现。以下是一个例子:

public void doSomething() throws MyException {
   
    try {
   
        // Some code that may throw an exception
    } catch (Exception e) {
   
        throw new MyException("Something went wrong", e);
    }
}

在这个例子中,我们定义了一个名为MyException的异常类,并在doSomething()方法中抛出它。在catch块中,我们使用throw语句抛出MyException异常,并将原始异常作为参数传递给它。这使得调用者可以了解发生的异常情况。

除了再次抛出异常外,Java还提供了一种称为异常链的机制,可以通过它将原始异常与新异常相关联。我们可以通过将原始异常作为参数传递给新异常的构造函数来实现异常链。以下是一个示例:

public void doSomething() throws MyException {
   
    try {
   
        // some code that may throw an exception
    } catch (Exception e) {
   
        throw new MyException("Something went wrong", e); // 将原始异常作为参数传递给新异常
    }
}

// 定义一个新的异常类,并将原始异常与新异常相关联
public class MyException extends Exception {
   
    public MyException(String message, Throwable cause) {
   
        super(message, cause);
    }
}

在这个例子中,我们将原始异常作为参数传递给MyException异常的构造函数。这将创建一个异常链,该链将新异常与原始异常相关联。这使得我们可以在调用者处看到整个异常链,以便更好地了解发生的异常情况。

7.2.4 finally子句

finally子句是Java异常处理机制中的一部分,它通常用于在try-catch块中执行清理操作。无论在try块中是否有异常抛出,finally子句中的代码都会被执行。它通常用于释放资源,关闭文件和网络连接等操作,以确保在程序执行完毕时,这些资源被正确清理。

以下是一个使用finally子句的例子:

public void readFile() {
   
    FileReader reader = null;
    try {
   
        reader = new FileReader("file.txt");
        // Some code that may throw an exception
    } catch (IOException e) {
   
        // Handle the exception
    } finally {
   
        try {
   
            if (reader != null) {
   
                reader.close();
            }
        } catch (IOException e) {
   
            // Handle the exception
        }
    }
}

在这个例子中,我们打开一个文件并尝试读取它。如果读取时出现异常,我们会捕获它并处理它。无论在try块中是否有异常抛出,finally子句中的代码都会被执行,以确保文件读取器被关闭。在finally块中,我们使用try-catch块来关闭文件读取器并处理任何可能抛出的IOException异常。

请注意,即使在try块中有return语句,finally子句中的代码也会被执行。这是Java中的规则,以确保在方法返回之前执行任何必要的清理操作,例如关闭资源等。

总之,finally子句是Java异常处理机制中的一个重要组成部分,它确保在程序执行完毕时清理资源。

7.2.5 try - with - Resources 语句

Java中的try-with-resources语句是一种在Java 7中引入的语法,它提供了一种简洁而安全的方式来处理Java程序中可能发生的资源泄漏问题。在try-with-resources中,我们可以将需要在try语句中打开或分配的资源声明为局部变量,这样编译器会自动负责管理这些资源的释放,从而避免了手动释放资源时可能出现的错误。

以下是一个使用try-with-resources语句处理文件IO操作的示例:

try (FileInputStream inputStream = new FileInputStream("input.txt")) {
    // 处理文件输入流
} catch (IOException e) {
    // 处理异常
}

在这个示例中,我们声明了一个FileInputStream对象并使用它来读取文件input.txt的内容。由于FileInputStream实现了AutoCloseable接口,所以我们可以在try语句中声明这个对象,并在try语句结束时自动关闭它。如果在try语句执行期间出现IOException异常,这个异常会被捕获并在catch语句中处理。

Java中的try-with-resources语句主要用于简化代码并确保资源的正确关闭。以下是一些try-with-resources的应用场景:

  1. 文件IO操作

在try-with-resources语句中使用FileInputStream或FileOutputStream可以避免手动关闭文件流造成的资源泄漏问题。例如:

try (FileInputStream input = new FileInputStream("file.txt")){
   
    // 处理文件输入流
} catch (IOException e) {
   
    // 处理异常
}
  1. 数据库连接

在try-with-resources语句中使用类似于Connection或Statement的JDBC对象可以确保数据库连接在不再使用时被正确关闭。例如:

try (Connection conn = DriverManager.getConnection(url, user, password);
     Statement stmt = conn.createStatement()) {
   
    // 处理数据库请求
} catch (SQLException e) {
   
    // 处理异常
}
  1. 网络连接

在try-with-resources语句中使用类似于Socket或ServerSocket的网络连接对象可以确保网络连接在不再使用时被正确关闭。例如:

try (Socket socket = new Socket("localhost", 1234)){
   
    // 处理网络连接
} catch (IOException e) {
   
    // 处理异常
}

通过使用try-with-resources语句,代码变得更加简洁,并且可以确保资源在正确的时间被释放。

7.2.6 分析堆栈轨迹元素

堆栈轨迹元素通常包括以下几个部分:

  1. 函数名称或方法名:指明正在执行的函数或方法的名称。

  2. 文件名和行号:指明正在执行的代码所在的文件和具体行号。

  3. 调用函数或方法的位置:指明该函数或方法是被哪个函数或方法调用的。

  4. 堆栈帧或栈帧信息:包括当前函数的局部变量和参数,以及返回地址等信息。

通过对堆栈轨迹元素的分析,我们可以追溯程序执行时出现问题的位置和原因,有助于我们进行调试和排除bug。例如,根据堆栈轨迹元素可以判断是哪个函数或方法出现了异常,以及异常发生的具体位置,有利于我们快速定位和解决问题。

在Java中,可以通过异常对象获取堆栈轨迹信息,其中包含了方法调用栈上的方法名、类名、文件名和行号等信息。可以通过该信息快速定位代码中的问题。

以下是一个简单的示例,可以使用它来获取和分析异常的堆栈轨迹元素:

try {
   
    // 代码片段
} catch (Exception e) {
   
    for (StackTraceElement element : e.getStackTrace()) {
   
        System.out.println(element.getClassName() + " - " 
            + element.getMethodName() + " - " 
            + element.getFileName() + ":" 
            + element.getLineNumber()); 
    }
}

该代码块将打印出每个堆栈轨迹元素的类名、方法名、文件名和行号。通过分析这些元素,可以快速定位代码中的问题。

另外,如果你想在代码中手动创建堆栈轨迹信息,可以使用Thread类的getStackTrace()方法。例如:

StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
   
    System.out.println(element.getClassName() + " - " 
        + element.getMethodName() + " - " 
        + element.getFileName() + ":" 
        + element.getLineNumber());
}

这将打印当前线程的所有堆栈轨迹元素。

7.2.7 Java检测错误的api

API名称 描述
assert 判断表达式是否为 true
try-catch 捕获和处理异常
throws 方法声明中声明可能抛出的异常
finally 无论是否有异常,最终都执行的代码块
IllegalArgumentException 当向方法传递错误的参数或参数不合法时抛出
NullPointerException 当应用程序试图在需要对象的地方使用 null 时抛出
IndexOutOfBoundsException 当索引超出范围时抛出
UnsupportedOperationException 不支持的操作异常
ArithmeticException 当发生异常的算术条件,如被零除时抛出
ClassCastException 当应用程序试图将对象强制转换为不是实例的子类时抛出
SecurityException 当安全性被违反时抛出
FileNotFoundException 当试图打开指定路径名表示的文件失败时抛出
IOException 当发生某种 I/O 异常时抛出

7.3 使用异常的技巧

Java中使用异常的技巧包括:

  1. 对于可能会抛出异常的代码块,使用try-catch语句进行包裹,以处理异常情况。

  2. 尽量使用标准异常类,如NullPointerException、IndexOutOfBoundsException等,而不是自定义异常。这样可以更好地符合Java编程规范,也更容易理解和维护。

  3. 在catch块中不要忽略异常,而应该对异常进行处理或记录。这有助于定位和解决程序中的问题。

  4. 使用finally语句块来确保程序的资源被正确释放,比如关闭文件句柄、释放数据库连接等。

  5. 继承Exception类而非Throwable类来定义自定义异常。这样可以提高代码的可读性和可维护性。

  6. 不要滥用异常。只有在必要的时候才使用异常,不应将其用作控制流程的一种方式。

  7. 使用有意义的异常信息。抛出异常时,应该提供有意义的异常信息,以帮助调用者快速定位问题。

  8. 尽量避免在循环体内抛出异常,这样会增加代码的复杂度和异常的捕获次数。在可能引发异常的地方,可以通过检查条件来预先避免异常的发生。

总之,合理使用异常可以提高程序的健壮性和可维护性,减少程序出错的概率。

7.4 断言

Java断言是一种调试技术,可以在代码中加入一些断言检查来确保程序的正确性。Java断言的好处包括:

  1. 提高代码的可靠性:断言可以检查程序中的一些前置条件、后置条件或不变量是否满足,从而在代码运行期间检测出一些错误或潜在的问题,提高代码的可靠性。

  2. 方便调试:在程序正式发布之前,开发人员可以通过断言检查来确保程序的正确性。如果断言检查失败,可以方便地定位问题并进行调试。

  3. 提高代码的可维护性:通过断言检查,可以发现代码中的一些问题,并对代码进行修复或优化,从而提高代码的可维护性。

  4. 启发编程思路:在编写代码时,加入一些断言检查可以更好地理解问题的本质,并启发编程思路,提高代码的质量和效率。

总之,Java断言是一种强大的调试技术,可以帮助开发人员在程序开发过程中发现问题并及时修复,提高代码的可靠性和可维护性。

7.4.1 断言的概念

Java断言是一种用于调试和测试的机制,它允许程序员在代码中插入一些条件检查,以确保代码的正确性。断言是一种在程序运行时检查条件是否为真的方法,如果条件为假,则会抛出一个AssertionError异常。断言可以用来检查程序中的逻辑错误、算法错误或者一些假设是否成立。在Java中,断言可以使用assert关键字来声明,基本语法如下:

assert condition;

其中,condition是需要检查的条件表达式。当condition为false时,程序会抛出AssertionError异常。在需要关闭断言时,可以使用-java:disableassertions或-Djava .assertions=disabled选项来禁用它们。通常情况下,断言主要用于开发和测试阶段,可以在发布时禁用它们以提高程序的性能。

7.4.2 启用和禁用断言

在Java中,默认情况下启用断言。可以使用以下命令行参数来显式禁用、启用或选择性地启用断言。

  • 关闭断言:-da-disableassertions
  • 开启断言:-ea-enableassertions
  • 选择性开启断言:使用-ea:后跟包名或类名来选择性启用该包或该类的断言,例如-ea:com.example-ea:com.example.MyClass

示例命令行:

  • 不启用断言:java -da MyApp
  • 启用所有断言:java -ea MyApp
  • 启用特定包的断言:java -ea:com.example MyApp

7.4.3 使用断言完成参数检查

在Java中,可以使用断言来完成参数检查,这样可以在程序运行时确保输入参数的合法性,从而避免在处理参数时出现错误。以下是使用断言完成参数检查的方法和好处:

方法:

在方法的开头使用 assert 关键字进行参数检查,如果检查失败就抛出 AssertionError 异常。示例代码如下:

public void doSomething(int number) {
   
    assert number > 0 : "Number must be positive";
    // ... 执行操作 ...
}

好处:

  1. 程序的健壮性更高:通过断言可以在程序运行时更及时地发现潜在的问题,避免在后续的操作中出现难以定位的错误,从而提高了程序的健壮性。

  2. 提高开发效率:使用断言可以快速发现问题所在,有助于开发人员更快地定位和解决问题,从而提高开发效率。

  3. 更加客观的判断:通过断言可以确保参数的合法性,从而让程序更加客观,减少因为人为原因导致的错误。

需要注意的是,在正式发布的产品代码中,应该关闭断言以提高程序的性能,避免不必要的开销。

7.4.4 使用断言提供假设文档

假设文档是一份涵盖系统或软件功能、需求和限制的文档。以下是一个假设文档的示例,其中使用了断言:

假设:系统将允许用户创建和保存个人资料。

断言:

  1. 用户必须能够通过系统界面访问个人资料创建和编辑页面。
  2. 系统必须能够验证用户输入的个人资料,包括姓名、地址和联系方式等。
  3. 系统必须能够存储用户的个人资料,并在需要时将其呈现给用户。
  4. 用户必须能够使用系统的搜索功能搜索其个人资料。
  5. 系统必须保护用户个人资料的隐私和安全,防止未经授权的访问或泄露。
  6. 系统必须为用户提供选择是否将其个人资料共享给其他用户或第三方的选项。

以上断言的目的是确保系统具有所需的功能和保证用户体验和数据的可靠性和安全性。

Java中的断言用于在代码中检查和验证假设条件。使用断言可以帮助程序员确定代码是否按照预期执行。在Java开发中,我们可以使用断言来提供假设文档,如下所示:

  1. 假设我们有一个名为“Student”的类,我们想要为该类的“age”属性添加一个断言,以确保年龄始终大于等于0:
public class Student {
   
    private int age;

    public void setAge(int age) {
   
        assert age >= 0 : "Age cannot be negative";
        this.age = age;
    }

    // rest of the class
}
  1. 在上述示例中,我们使用了Java中的“assert”语句来添加断言。如果年龄小于0,则会抛出一个AssertionError异常,并显示指定的错误消息。

  2. 可以将断言用于任何条件,例如方法的参数或方法的返回值。断言应该用于检查假设的条件,而不是用于处理运行时错误。

  3. 启用或禁用断言可以通过JVM参数“-ea”或“-enableassertions”来控制。默认情况下,断言是禁用的,在发布代码时应该禁用断言。在开发和测试阶段启用断言可以帮助我们查找和解决问题。

7.5 日志

日志是在软件系统中记录和跟踪事件、操作、状态和错误等信息的重要手段。它可以帮助开发人员了解系统的运行情况、调试和解决问题、优化系统性能和安全性,同时也为用户提供了更高效和负责任的技术支持。

在Java开发中,通常使用日志框架,如Log4j、Logback、SLF4j等来实现日志记录。这些框架提供了丰富的日志级别、格式、输出位置和存储方式等配置选项,同时也支持日志打印过滤、异步记录和可扩展性等功能。

以下是几种常见的Java中使用日志的场景:

  1. 调试和错误追踪:通过记录系统运行时的异常、堆栈信息、输入输出和业务流程等日志,帮助开发人员查找和解决问题。

  2. 性能分析:通过记录系统的响应时间、资源利用率、并发情况和调用链路等信息,帮助开发人员优化系统性能和稳定性。

  3. 安全审计:通过记录用户行为、访问权限和操作记录等信息,帮助系统管理员监控和管理系统安全。

  4. 业务监控:通过记录业务指标、关键路径和交易流程等信息,帮助业务人员了解系统运行情况和用户需求。

综上所述,日志在Java开发中具有重要的作用,并且应该被广泛应用于软件系统的开发、测试、运维和支持等环节。

7.5.1 基本日志

Java中的基本日志记录可以使用内置的java.util.logging包。

以下是一个基本示例:

import java.util.logging.*;

public class MyLogger {
   
    static Logger logger = Logger.getLogger(MyLogger.class.getName());

    public static void main(String[] args) {
   
        logger.info("Hello World!");
    }
}

在此示例中,Logger对象被创建并使用其info()方法打印出一条日志消息。默认情况下,日志消息将发送到控制台。

Java日志系统支持多个日志级别,从低到高依次为:

  • SEVERE (最高级别)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST (最低级别)

可以使用Logger.setLevel()方法设置日志级别。

除了输出到控制台,还可以将日志消息输出到文件或远程服务器等位置。可以通过配置logging.properties文件来指定日志记录器的行为。例如,在logging.properties文件中,可以使用以下示例配置文件设置日志级别:

handlers= java.util.logging.ConsoleHandler
 
.level= INFO
 
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

有关更多信息,请参见Java文档中的Logger类和Logging Overview。

7.5.2 高级日志

Java中的高级日志记录可以使用第三方库,如log4j和logback。

这些库提供了更高级别的功能,例如:

  • 支持多个日志级别和自定义级别。
  • 支持不同的输出目标,例如控制台、文件、数据库等。
  • 支持不同的日志格式和布局。
  • 支持动态配置日志级别和其他细节。
  • 提供更好的性能和可靠性等。

以下是使用log4j记录日志的基本示例:

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class MyLogger {
   
    static Logger logger = Logger.getLogger(MyLogger.class);

    public static void main(String[] args) {
   
        BasicConfigurator.configure();
        logger.info("Hello World!");
    }
}

在此示例中,Logger对象被创建并使用其info()方法打印出一条日志消息。BasicConfigurator.configure()方法被用来配置log4j库,使其使用默认设置。可以使用log4j.properties文件来自定义log4j的配置。

有关更多信息,请参见log4j和logback的文档。

7.5.3 修改日志管理器配置

  1. 打开日志管理器的配置文件,通常命名为log4j.properties或log4j.xml。

  2. 设置输出的目标(输出到控制台、文件、数据库等)。

例如,将日志输出到控制台:

log4j.rootLogger=INFO, stdout

# 输出到标准输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender

# 控制台输出格式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
  1. 配置日志的级别和输出格式。

例如,设置日志级别为DEBUG:

# 设置日志级别为DEBUG
log4j.rootLogger=DEBUG, stdout

# 输出格式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
  1. 配置不同类的日志级别和输出目标。

例如,将com.example包下的日志输出到文件:

# 设置根日志级别为INFO, 输出到控制台
log4j.rootLogger=INFO, stdout

# 设置com.example包的日志级别为DEBUG, 输出到文件
log4j.logger.com.example=DEBUG, file

# 输出到文件
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=logs/example.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
  1. 保存配置文件并重启应用程序以应用更改。

7.5.4 本地化

Java 日志本地化可通过以下步骤实现:

  1. 在 Java 应用程序中使用国际化 (i18n) 和本地化 (l10n) 相关 API,如 ResourceBundle 和 Locale 类;

  2. 在应用程序的日志配置文件中指定日志消息的本地化属性,如以下示例所示:

# 指定日志消息的本地化属性文件路径
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format= %1$tc %4$s [%3$s] %5$s %n
java.util.logging.FileHandler.encoding=UTF-8
java.util.logging.FileHandler.level= INFO
java.util.logging.FileHandler.limit= 50000000
java.util.logging.FileHandler.count= 5
java.util.logging.FileHandler.pattern= logs/app-%g.log

# 指定日志消息的本地化语言
java.util.logging.SimpleFormatter.localizations = zh_CN
  1. 创建适当的本地化资源文件,其中包含与应用程序相关的日志消息的本地化版本,如以下示例所示:
# 异常消息
test.exception=在执行操作时发生错误:{0}

# 警告消息
test.warning=警告:{0}

# 信息消息
test.info=正在执行操作:{0}
  1. 在应用程序中使用本地化的日志消息,如以下示例所示:
Logger logger = Logger.getLogger(MyClass.class.getName());
String bundleName = "com.example.resources.messages";
ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
String message = bundle.getString("test.info");
logger.log(Level.INFO, message, new Object[]{"操作名称"});

7.5.5 处理器

Java日志处理器是用于捕获和处理Java应用程序中的日志信息的工具。Java应用程序通常使用日志记录器将特定事件和错误记录到日志文件中,以便开发人员进行故障排除和问题分析。

Java中有多个内置的日志处理器,如Java Logging API、Log4j、SLF4J等。它们都提供了用于记录日志信息的方法,并且可以配置不同级别的日志记录,如调试、信息、警告和错误。

Java Logging API是Java SE的一部分,提供了一个标准的日志API,可以在Java应用程序中使用。它提供了丰富的配置选项,可以将日志信息输出到控制台、文件、数据库或其他目标。

Log4j是一个流行的日志记录框架,提供了更丰富的功能,如异步日志记录、多个日志输出端口、动态配置等。

SLF4J是一个简单的日志API,提供了与不同日志框架的兼容性,使开发人员可以更轻松地在不同的项目中切换日志框架。

总之,选择合适的日志处理器,可以帮助开发人员更好地监测和调试应用程序,并提高应用程序的可靠性和可维护性。

下面是一个简单的Java实现日志处理器的示例:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Logger {
   
    
    private static final String LOG_FILE = "application.log";
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    
    public static void log(String message) {
   
        try {
   
            String formattedMessage = String.format("%s - %s", DATE_FORMAT.format(new Date()), message);
            File logFile = new File(LOG_FILE);
            BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true));
            writer.write(formattedMessage);
            writer.newLine();
            writer.close();
        } catch (IOException e) {
   
            e.printStackTrace();
        }
    }
}

该示例实现了一个简单的日志处理器,将日志消息写入文件中,以时间戳和消息文本作为日志行的格式。

要使用该日志处理器,只需调用Logger.log()方法,该方法将消息作为其参数,并将其附加到日志文件中。

例如:

Logger.log("An error occurred while processing the request");

7.5.6 过滤器

日志过滤器是Java中用于过滤和控制输出的一种机制。在Java的日志框架中,可以使用过滤器来选择并控制哪些日志消息应该被记录和输出,哪些应该被忽略。

Java日志框架的常用过滤器有以下几种:

  1. LevelFilter:该过滤器基于日志消息的级别来过滤消息。可以使用该过滤器来记录特定级别的消息,例如只记录严重的错误或警告。

  2. LoggerNameFilter:该过滤器基于日志消息的Logger名称来过滤消息。可以在Logger名称上设置过滤器,以便只记录特定的日志消息。

3.RegExpFilter:该过滤器使用正则表达式来匹配日志消息。可以使用该过滤器来过滤特定的文本或事件。

  1. MDCFilter:该过滤器使用MDC(Mapped Diagnostic Context)来过滤日志消息。 MDC是一个线程局部变量池,在其中可以存储和检索与特定线程相关的上下文信息。可以使用该过滤器来记录特定的上下文信息。

  2. AndFilter:该过滤器允许将多个过滤器组合为一个逻辑与“AND”的组合。只有当所有过滤器都匹配时,日志消息才会被记录和输出。

  3. OrFilter:该过滤器允许将多个过滤器组合为一个逻辑或“OR”的组合。只要有一个过滤器匹配,就会记录和输出日志消息。

使用Java日志框架的过滤器可以根据需求来灵活地控制输出,提高日志记录的效率和可读性。

这是一个基本的日志过滤器 Java 实例,可以根据日志级别过滤出需要的日志信息。

import java.util.logging.Filter;
import java.util.logging.LogRecord;

public class LogLevelFilter implements Filter {
   

    private final Level level;

    public LogLevelFilter(Level level) {
   
        this.level = level;
    }

    @Override
    public boolean isLoggable(LogRecord record) {
   
        return record.getLevel().intValue() >= level.intValue();
    }
}

使用示例:

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main {
   

    private static final Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) {
   
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.ALL);

        LogLevelFilter infoFilter = new LogLevelFilter(Level.INFO);
        consoleHandler.setFilter(infoFilter);

        logger.addHandler(consoleHandler);

        logger.log(Level.SEVERE, "This is a SEVERE log.");
        logger.log(Level.INFO, "This is an INFO log.");
        logger.log(Level.FINE, "This is a FINE log.");
    }
}

上面的示例将只输出 INFO 及以上级别的日志信息。

7.5.7 格式化器

日志格式化器是一种将日志消息转换为特定格式的工具。通常,日志消息包含非常详细的信息,例如日期时间、日志级别、类名、方法名、线程名和消息文本等,格式化器可以帮助我们将这些信息组合成易于阅读和分析的格式。

以下是一个简单的Java实现日志格式化器的示例:

import java.text.SimpleDateFormat;
import java.util.Date;

public class LogFormatter {
   
    
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    
    public static String format(String message, String level, String className, String methodName) {
   
        String timestamp = DATE_FORMAT.format(new Date());
        return String.format("[%s] [%s] [%s.%s] %s", timestamp, level, className, methodName, message);
    }
}

该示例实现了一个简单的日志格式化器,它将消息、日志级别、类名、方法名和日期时间戳组合在一起,用方括号括起来,并用空格分隔,以便于阅读和分析。

要使用该日志格式化器,请将需要格式化的信息传递给format()方法,并传递消息、日志级别、类名和方法名作为参数。例如:

String message = "An error occurred while processing the request";
String level = "ERROR";
String className = "com.example.MyClass";
String meth
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值