Java异常
在进行 Java 高级开发时,掌握异常相关的知识点非常重要,这些知识点包括但不限于:
-
异常类层次结构:理解 Java 异常类的继承关系,包括
Throwable
、Exception
和Error
。 -
常见异常类型:了解常见的异常类型,如
NullPointerException
、ArrayIndexOutOfBoundsException
、IOException
等,以及它们的产生原因和解决方法。 -
异常处理机制:掌握使用
try-catch-finally
块来处理异常的基本语法,以及如何抛出异常和捕获异常。 -
自定义异常:了解如何创建自定义异常类,以便在需要时抛出自定义异常并进行处理。
-
异常处理的最佳实践:掌握异常处理的最佳实践,包括避免过多捕获异常、适当处理异常、及时释放资源等。
-
异常链:理解异常链的概念,即一个异常引发另一个异常的情况,以及如何使用
Throwable
类的构造方法来建立异常链。 -
异常处理工具:熟悉常用的异常处理工具,如日志记录工具(如 Log4j、Logback)和调试工具(如 Eclipse、IntelliJ IDEA)。
-
异常处理与性能:了解异常处理对性能的影响,包括捕获异常的成本、异常处理的开销以及如何在性能和可维护性之间取得平衡。
-
异常处理与多线程:了解在多线程环境下的异常处理方式,包括如何在多线程中正确捕获和处理异常,以及如何避免异常导致的线程安全问题。
-
异常处理的设计模式:了解异常处理的设计模式,如异常转换、异常屏蔽、异常重试等,以及如何根据具体情况选择合适的设计模式来处理异常。
面试题
1. throw和throws的区别
throw
和 throws
是 Java 中与异常处理相关的两个关键字,它们的作用和用法略有不同。
throw 关键字
throw
关键字用于手动抛出一个异常对象。- 通常在代码中遇到某些错误或特定条件时,程序员可以使用
throw
关键字显式地抛出一个异常。 throw
后面跟着要抛出的异常对象,可以是 Java 内置的异常类对象,也可以是自定义的异常对象。- 抛出异常后,程序会立即终止当前方法的执行,并且异常对象会被传递给调用者。
示例:
public void someMethod() {
if (condition) {
throw new IllegalArgumentException("Invalid argument!");
}
}
throws 关键字
throws
关键字用于在方法签名中声明可能会抛出的异常。- 当一个方法可能会抛出某些异常,但是不处理这些异常,而是将异常传递给调用者处理时,可以在方法声明中使用
throws
关键字来声明可能抛出的异常。 throws
后面跟着的是异常类的列表,表示该方法可能会抛出的异常类型。- 方法中实际抛出异常的地方需要使用
throw
关键字抛出异常,而throws
只是在方法签名中声明可能抛出的异常,不会真正抛出异常。
示例:
public void readFile() throws IOException {
// 读取文件的代码,可能会抛出 IOException
}
区别总结
throw
用于手动抛出一个异常对象,而throws
用于声明方法可能抛出的异常。throw
是在方法体内部使用的,而throws
是在方法签名中使用的。throw
表示抛出一个异常,而throws
表示声明方法可能抛出的异常。
2. Throwable, Exception, Error
在 Java 中,Throwable
、Exception
和 Error
是异常处理机制中的三个重要类,它们之间有着明显的区别。
-
Throwable:
Throwable
是 Java 异常类层次结构的根类,它是所有异常的基类。Throwable
类有两个重要的子类:Error
和Exception
。Throwable
类中定义了一些重要的方法,如getMessage()
、printStackTrace()
等,用于获取异常信息和打印异常堆栈信息。
-
Exception:
Exception
类表示程序运行期间可能出现的异常情况,是可以被程序员预见和处理的异常。Exception
及其子类是由程序逻辑或外部因素(如用户输入、网络连接)导致的,通常需要在代码中进行处理。Exception
类有许多子类,如IOException
、NullPointerException
、IllegalArgumentException
等,分别表示不同类型的异常情况。
-
Error:
Error
类表示严重的问题,通常由 Java 虚拟机(JVM)或其他系统组件引起,程序一般无法处理。Error
类和其子类通常用于指示系统级的错误或运行时环境的严重问题,如内存溢出、栈溢出等。- 与
Exception
不同,Error
及其子类通常不需要程序员捕获和处理,因为它们表示程序无法恢复的情况。
区别总结:
Throwable
是异常类层次结构的根类,Exception
和Error
都是Throwable
的子类。Exception
表示程序运行期间可能出现的异常情况,需要程序员捕获和处理;而Error
表示严重的问题,通常由 JVM 或系统组件引起,程序一般无法处理。Exception
及其子类通常是由程序逻辑或外部因素引起的,需要在代码中进行处理;而Error
及其子类通常是系统级的错误或环境问题,通常不需要程序员处理。
3. 常见异常
Java 中常见的异常分为两大类:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions)。受检异常需要在代码中明确处理或声明,非受检异常则不需要强制处理。以下是常见的异常类型及其简要说明:
受检异常(Checked Exceptions)
-
IOException
- 描述:处理输入输出操作失败或中断的异常。
- 常见子类:
- FileNotFoundException
- EOFException
-
SQLException
- 描述:数据库访问错误或其他数据库访问错误时抛出。
-
ClassNotFoundException
- 描述:找不到指定的类时抛出。
-
InstantiationException
- 描述:试图使用
Class.newInstance()
创建一个抽象类或接口的实例时抛出。
- 描述:试图使用
-
IllegalAccessException
- 描述:尝试访问某个类的私有字段或方法时抛出。
-
InvocationTargetException
- 描述:通过反射调用方法时抛出异常时的包装异常。
非受检异常(Unchecked Exceptions)
-
NullPointerException
- 描述:引用空对象时抛出。
-
ArrayIndexOutOfBoundsException
- 描述:访问数组的非法索引时抛出。
-
ArithmeticException
- 描述:算术运算错误(如除零)时抛出。
-
IllegalArgumentException
- 描述:方法传递的参数不合法或不正确时抛出。
-
IllegalStateException
- 描述:方法在非法或不适当的时间被调用时抛出。
-
ClassCastException
- 描述:试图将对象强制转换为不是实例的类时抛出。
-
NumberFormatException
- 描述:尝试将字符串转换为数值类型失败时抛出。
-
IndexOutOfBoundsException
- 描述:索引超出范围时抛出,常见子类:
- StringIndexOutOfBoundsException
- ArrayIndexOutOfBoundsException
- 描述:索引超出范围时抛出,常见子类:
-
UnsupportedOperationException
- 描述:不支持某个操作时抛出。
-
ConcurrentModificationException
- 描述:检测到对象在遍历期间被修改时抛出(例如
ArrayList
的迭代器)。
- 描述:检测到对象在遍历期间被修改时抛出(例如
错误(Errors)
-
StackOverflowError
- 描述:栈内存溢出时抛出,通常是由于递归调用没有终止。
-
OutOfMemoryError
- 描述:Java 虚拟机内存耗尽时抛出。
-
VirtualMachineError
- 描述:虚拟机错误的父类,如
OutOfMemoryError
和StackOverflowError
。
- 描述:虚拟机错误的父类,如
-
AssertionError
- 描述:断言失败时抛出,通常用于调试目的。
-
NoClassDefFoundError
- 描述:JVM 或类加载器在试图加载类时,找不到类的定义时抛出。
示例代码
以下是一些常见异常的示例代码:
public class ExceptionExamples {
public static void main(String[] args) {
// NullPointerException
try {
String str = null;
str.length();
} catch (NullPointerException e) {
System.out.println("Caught NullPointerException: " + e.getMessage());
}
// ArrayIndexOutOfBoundsException
try {
int[] array = new int[5];
int value = array[10];
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage());
}
// NumberFormatException
try {
int number = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Caught NumberFormatException: " + e.getMessage());
}
// IOException (Checked Exception)
try {
throw new java.io.IOException("IO error");
} catch (java.io.IOException e) {
System.out.println("Caught IOException: " + e.getMessage());
}
}
}
4. 异常处理最佳实践
在 Java 中,异常处理是编写健壮和可维护代码的重要部分。以下是一些异常处理的最佳实践:
1. 使用受检异常和非受检异常
- 受检异常(Checked Exceptions):用于可以合理预期的异常,如 IO 操作失败。需要显式捕获或声明。
- 非受检异常(Unchecked Exceptions):用于编程错误,如
NullPointerException
、IllegalArgumentException
,通常不强制捕获。
2. 捕获特定异常
- 避免捕获
Exception
或Throwable
,只捕获你能够处理的特定异常类型。
try {
// code that might throw an exception
} catch (IOException e) {
// handle IOException
} catch (SQLException e) {
// handle SQLException
}
3. 使用 finally 块释放资源
- 确保在
finally
块中释放资源(如文件、数据库连接),以避免资源泄露。
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// Read file
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 避免空的 catch 块
- 捕获异常后不做任何处理可能会导致程序难以调试和维护。
try {
// code that might throw an exception
} catch (IOException e) {
e.printStackTrace(); // 或者记录日志
}
5. 提供有意义的异常信息
- 抛出异常时提供详细的错误信息,有助于调试和问题定位。
if (someCondition) {
throw new IllegalArgumentException("Invalid argument: " + argument);
}
6. 使用自定义异常
- 创建自定义异常类,使异常信息更具体化和可读。
public class InvalidUserInputException extends Exception {
public InvalidUserInputException(String message) {
super(message);
}
}
7. 记录异常
- 使用日志框架(如 SLF4J、Log4J)记录异常,有助于后续问题排查。
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
try {
// code that might throw an exception
} catch (IOException e) {
logger.error("IO error occurred", e);
}
8. 不要忽略异常
- 捕获异常后应采取相应的措施,不能简单地忽略它们。
try {
// code that might throw an exception
} catch (Exception e) {
// Don't ignore the exception
handleException(e);
}
9. 将异常转换为有意义的异常
- 将低级别的异常转换为更高层次的异常,以提供更有意义的错误信息。
try {
// code that might throw an IOException
} catch (IOException e) {
throw new ServiceException("Failed to process request", e);
}
10. 避免过度使用异常
- 不要将异常用于正常的控制流,异常处理通常较为昂贵。
// 不推荐的方式
try {
int value = Integer.parseInt(input);
} catch (NumberFormatException e) {
// handle exception
}
// 推荐的方式
if (input.matches("\\d+")) {
int value = Integer.parseInt(input);
} else {
// handle invalid input
}
11. 使用 try-with-resources
- 在 Java 7 及以后版本中,使用
try-with-resources
语句自动关闭资源。
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// Read file
} catch (IOException e) {
e.printStackTrace();
}
12. 捕获并重新抛出异常
- 在某些情况下,捕获异常后需要重新抛出它,以便上层调用者能够处理。
try {
// code that might throw an exception
} catch (SQLException e) {
logger.error("Database error occurred", e);
throw e; // rethrow the exception
}
5. 什么是资源泄漏?
在 finally
块中关闭 IO 资源是一种重要的编程实践,未能这样做可能会导致资源泄漏。
什么是资源泄漏?
资源泄漏是指程序未能释放已经分配的资源(如文件、网络连接、数据库连接等),从而导致资源的耗尽。这种情况会导致系统性能下降,甚至可能导致程序崩溃。
为什么未关闭 IO 资源会造成资源泄漏?
-
资源没有及时释放:
- IO 资源(如文件、网络连接、数据库连接)在操作系统中是有限的。未能关闭这些资源会导致它们无法被其他进程或线程使用,最终可能耗尽系统资源。
-
占用内存:
- 没有及时关闭的资源会占用内存,可能导致内存泄漏,使系统性能下降。
-
系统文件描述符耗尽:
- 每个打开的文件或网络连接都会占用一个文件描述符。如果文件描述符耗尽,系统将无法打开新的文件或建立新的网络连接。
-
数据不一致:
- 尤其在文件和数据库操作中,未能正确关闭资源可能导致数据未能正确刷新到磁盘,从而导致数据不一致或损坏。
示例
以下是一个未在 finally
块中关闭资源的示例:
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
// 没有在 finally 块中关闭 fis
如果在 try 块中的代码抛出了异常,程序将跳过关闭资源的代码,从而导致 FileInputStream
没有被关闭。
如何正确关闭资源
使用 finally
块确保资源被关闭:
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 读取文件
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用 try-with-resources
在 Java 7 及以后的版本中,可以使用 try-with-resources
语句自动关闭资源:
try (FileInputStream fis = new FileInputStream("file.txt")) {
// 读取文件
} catch (IOException e) {
e.printStackTrace();
}
try-with-resources
语句会在 try
块执行结束后自动关闭 FileInputStream
,无论是否发生异常。
总结
- 及时释放资源:未能及时释放 IO 资源会导致资源泄漏,影响系统性能。
- 使用
finally
块:确保在finally
块中关闭资源,以确保资源在任何情况下都被释放。 - try-with-resources:使用
try-with-resources
语句可以更简洁和安全地管理资源,避免资源泄漏。
6. 异常链
异常链(Exception Chaining)是指在捕获一个异常时,将另一个异常作为其原因(cause)进行传播的机制。这种技术使得异常处理变得更加灵活和信息丰富,因为它能够保留异常的原始上下文和详细信息。当一个异常导致了另一个异常时,使用异常链可以帮助我们追踪到最原始的异常来源,便于调试和问题排查。
构建异常链
在 Java 中,构建异常链的方式主要是通过异常的构造方法和 initCause()
方法。
1. 使用构造方法传递原因
很多标准异常类都有一个构造方法,可以接受另一个 Throwable
作为参数,这个 Throwable
就是导致当前异常的原因。例子如下:
public class ExceptionChainingExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void method1() throws Exception {
try {
method2();
} catch (Exception e) {
throw new Exception("Exception in method1", e);
}
}
public static void method2() throws Exception {
throw new Exception("Exception in method2");
}
}
在这个例子中,method2
抛出一个异常,然后 method1
捕获这个异常并抛出一个新的异常,同时将原始异常作为原因传递给新的异常。
2. 使用 initCause()
方法
有时候,我们可能需要在异常对象已经创建之后再设置其原因。在这种情况下,可以使用 initCause()
方法:
public class ExceptionChainingExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void method1() throws Exception {
try {
method2();
} catch (Exception e) {
Exception newException = new Exception("Exception in method1");
newException.initCause(e);
throw newException;
}
}
public static void method2() throws Exception {
throw new Exception("Exception in method2");
}
}
在这个例子中,我们先创建了一个新的异常对象,然后使用 initCause()
方法将原始异常设置为其原因。
捕获异常链
当捕获异常链时,可以使用 getCause()
方法获取原因异常,并进行相应的处理:
public class ExceptionChainingExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
Throwable cause = e.getCause();
while (cause != null) {
System.out.println("Caused by: " + cause);
cause = cause.getCause();
}
}
}
public static void method1() throws Exception {
try {
method2();
} catch (Exception e) {
throw new Exception("Exception in method1", e);
}
}
public static void method2() throws Exception {
throw new Exception("Exception in method2");
}
}
在这个例子中,我们捕获了异常链,并使用 getCause()
方法遍历并打印出每个原因异常。
7. 为什么要避免使用异常做流程判断?
捕获异常的成本
- 成本:捕获异常在运行时会产生一定的性能开销。Java 虚拟机在抛出和捕获异常时,需要进行栈的遍历、异常对象的创建和栈轨迹的填充,这些操作都需要额外的计算资源。
- 影响:在代码中频繁使用异常处理(尤其是在性能关键路径中)可能导致程序运行效率下降。因此,尽量避免在性能敏感的代码中使用异常来控制流程,而应通过其他手段(如条件判断)来避免异常的产生。
异常处理的开销
- 资源消耗:异常处理不仅仅是捕获异常的瞬间开销,还包括与之相关的日志记录、堆栈追踪信息的生成和处理等。这些操作会增加内存和 CPU 的消耗。
- 复杂性增加:复杂的异常处理机制(如深层次的异常链和过多的异常类型)可能增加代码的复杂性,进而影响代码的可维护性和理解性。
性能和可维护性之间的平衡
- 性能优化:在性能关键的应用场景中,应尽量减少异常的使用,避免通过异常来处理常规控制流。可以采用预检查条件的方式来避免异常的抛出和捕获。
- 可维护性:尽管异常处理有一定的性能开销,但它是确保系统健壮性和可维护性的重要手段。合理的异常处理可以帮助开发者快速定位问题并提供有用的错误信息。
- 平衡策略:开发者需要在性能和可维护性之间找到一个平衡点。在设计系统时,应考虑哪些地方需要严格的异常处理以确保系统的可靠性,哪些地方可以通过优化减少不必要的异常处理开销。
示例
考虑一个读取文件的例子:
性能敏感路径的优化
public String readFile(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
return null; // 提前检查,避免异常
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return reader.readLine();
} catch (IOException e) {
// 处理异常
return null;
}
}
过度依赖异常的写法
public String readFile(String filePath) {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
return reader.readLine();
} catch (FileNotFoundException e) {
// 文件不存在异常
return null;
} catch (IOException e) {
// 处理其他 IO 异常
return null;
}
}
在第二种写法中,代码依赖于异常处理来判断文件是否存在,而第一种写法通过预检查避免了 FileNotFoundException
的抛出,从而减少了性能开销。
8. 多线程环境下处理异常的最佳实践
在多线程环境下处理异常需要特别注意,因为异常可能会影响到多个线程的运行。以下是一些在多线程环境下处理异常的最佳实践:
1. 捕获和处理线程中的异常
每个线程都应该有自己的异常处理机制,以确保一个线程中的异常不会影响到其他线程。可以通过在 run()
方法中使用 try-catch
块来捕获和处理异常。
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
// 线程执行的代码
} catch (Exception e) {
// 处理异常
System.err.println("Thread encountered an exception: " + e.getMessage());
e.printStackTrace();
}
}
}
2. 使用未捕获异常处理器(UncaughtExceptionHandler)
Java 提供了 Thread.UncaughtExceptionHandler
接口,可以为每个线程或全局设置一个未捕获异常处理器。当线程在运行过程中抛出未捕获的异常时,JVM 会调用该处理器。
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("Thread " + t.getName() + " threw an exception: " + e.getMessage());
e.printStackTrace();
}
}
// 为单个线程设置未捕获异常处理器
Thread thread = new Thread(new MyRunnable());
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
thread.start();
// 为所有线程设置默认未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
3. 使用线程池处理异常
如果使用 ExecutorService
来管理线程池,可以通过提交任务时捕获 Future
对象中的异常。Future.get()
方法会在任务完成时抛出任何未捕获的异常。
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<?> future = executor.submit(new MyRunnable());
try {
future.get(); // 这里会捕获到线程执行中的任何异常
} catch (InterruptedException | ExecutionException e) {
System.err.println("Exception occurred: " + e.getMessage());
e.printStackTrace();
}
4. 优雅关闭线程池
在应用程序关闭时,应确保线程池被优雅地关闭,以确保所有任务都能正常完成或处理异常。
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
if (!executor.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Executor did not terminate");
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
5. 日志记录和监控
在多线程环境中,记录日志是非常重要的,可以帮助你在出现问题时快速定位异常发生的位置和原因。使用日志记录框架(如 Log4j、SLF4J 等)来记录线程中的异常信息。
private static final Logger logger = LoggerFactory.getLogger(MyRunnable.class);
@Override
public void run() {
try {
// 线程执行的代码
} catch (Exception e) {
logger.error("Thread encountered an exception: ", e);
}
}
通过这些最佳实践,你可以在多线程环境中更好地处理异常,确保应用程序的健壮性和稳定性。
9. 异常处理设计模式
在Java高级开发中,了解和使用一些异常处理设计模式能够提高代码的可维护性和可靠性。以下是一些常见的异常处理设计模式:
1. 异常转换(Exception Translation)
异常转换是将一种类型的异常转换为另一种更合适的异常类型,以便调用者更好地理解和处理异常。常用于将低级别的异常转换为高级别的业务异常。
public void someMethod() throws BusinessException {
try {
// 低级别的操作
} catch (SQLException e) {
throw new BusinessException("Database operation failed", e);
}
}
2. 异常封装(Exception Wrapping)
异常封装类似于异常转换,但它是将原始异常封装在新的异常中,同时保留原始异常的信息。这对于不想丢失异常链的情况非常有用。
public void someMethod() throws BusinessException {
try {
// 低级别的操作
} catch (SQLException e) {
throw new BusinessException("Database operation failed", e);
}
}
3. 异常屏蔽(Exception Shielding)
异常屏蔽是指在不希望调用者知道具体的异常细节时,将异常隐藏或转换为一个通用的异常信息。这样可以防止敏感信息泄露,提升安全性。
public void someMethod() throws BusinessException {
try {
// 低级别的操作
} catch (SQLException e) {
// 记录详细的异常信息,但抛出通用异常
logger.error("Database operation failed", e);
throw new BusinessException("An error occurred while processing your request");
}
}
4. 重试模式(Retry Pattern)
重试模式是在遇到临时性错误时,通过多次重试来增加操作成功的概率。这种模式常用于网络通信和数据库操作中。
public void someMethod() {
int retries = 3;
while (retries > 0) {
try {
// 尝试执行操作
break;
} catch (TemporaryException e) {
retries--;
if (retries == 0) {
throw new BusinessException("Operation failed after multiple attempts", e);
}
// 等待一段时间再重试
Thread.sleep(1000);
}
}
}
5. 资源清理(Resource Cleanup)
确保在发生异常时,任何打开的资源(如文件、数据库连接等)都能够被正确关闭,以避免资源泄漏。
public void someMethod() {
try (Connection connection = dataSource.getConnection()) {
// 使用数据库连接
} catch (SQLException e) {
throw new BusinessException("Database operation failed", e);
}
}
6. 哨兵模式(Sentinel Pattern)
哨兵模式使用特殊的对象(哨兵对象)来表示某种特殊情况,例如操作失败或未找到元素,而不是抛出异常。这种模式可以简化异常处理逻辑。
public Optional<Item> findItem(String itemId) {
try {
// 查找并返回项
} catch (ItemNotFoundException e) {
return Optional.empty();
}
}
7. 全局异常处理(Global Exception Handling)
使用框架(如Spring)提供的机制,在应用程序的一个集中位置处理所有未捕获的异常,统一处理逻辑并记录日志。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<String> handleBusinessException(BusinessException e) {
// 处理业务异常
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception e) {
// 处理其他异常
return new ResponseEntity<>("An error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
}
}