目录
1.异常(Throwable)
Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。
1.Error错误:(这种错误无法处理) 描述了Java运行时系统的内部错误和资源耗尽错误。一般是指虚拟机(JVM)相关的问题,如系统崩溃,虚拟机出错误等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常不处理。
2.Exception异常类又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
因此只注重Exception异常类的处理。。。我们所说的异常处理都是处理Exception下的异常。
1.1 Throwable 类的主要方法
1.2 Exception 类
我们所说的异常处理都是处理Exception下的异常。
1.2.1 异常处理
要理解 Java 异常处理是如何工作的,你需要掌握以下两种种类型的异常:
1.检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这些异常在编译时强制要求程序员处理。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
解决:这类异常通常使用 try-catch 块来捕获并处理异常,或者在方法声明中使用 throws 子句声明方法可能抛出的异常。
注意事项:检查性异常强制要求程序员必须处理。
2.运行时异常(非检查时异常): 这些异常在编译时不强制要求处理,通常是由程序中的错误引起的,例如 NullPointerException、ArrayIndexOutOfBoundsException 、ArithmeticException、ClassCastException等。
解决:这类异常可以选择处理,但并非强制要求。
1.2.2 检查性和非检查性异常的不同点
-
检查性异常的抛出:
- 如果方法可能会抛出检查性异常,必须在方法签名中使用
throws
关键字声明。 - 例如:
public void readFile() throws IOException { // 可能会抛出IOException的代码 }
- 调用这个方法的地方必须处理这个异常(使用
try-catch
)或者继续抛出。
- 如果方法可能会抛出检查性异常,必须在方法签名中使用
-
非检查性异常的抛出:
- 不需要在方法签名中声明抛出非检查性异常。
- 例如:
public void accessArrayElement(int[] array, int index) { if (index < 0 || index >= array.length) { throw new ArrayIndexOutOfBoundsException("Index out of bounds"); } }
- 调用这个方法的地方可以选择处理这个异常,但不强制要求。
1.2.3 异常处理关键字
Java 提供了以下关键字和类来支持异常处理:
- try:用于包裹可能会抛出异常的代码块。
- catch:用于捕获异常并处理异常的代码块。
- finally:用于包含无论是否发生异常都需要执行的代码块。释放资源。
- throw:用于手动抛出异常。通过手动创建异常对象。
- throws:用于在方法声明中指定方法可能抛出的异常。
- Exception类:是所有异常类的父类,它提供了一些方法来获取异常信息,如 getMessage()、printStackTrace() 等。
1.2.3.1 try-catch捕获异常
使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
使用 try/catch 的语法如下:
try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
}
注意事项:当try出现异常代码,异常代码下面的代码都不会被执行。
1.2.3.2 多重捕获块
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
多重捕获块的语法如下所示:
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}catch(异常类型3 异常的变量名3){
// 程序代码
}
执行说明:
上面的代码段包含了 3 个 catch块。
可以在 try 语句后面添加任意数量的 catch 块。
如果保护代码中发生异常,异常被抛给第一个 catch 块。
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
如果不匹配,它会被传递给第二个 catch 块。
如此,直到异常被捕获或者通过所有的 catch 块。
1.2.3.3 throws/throw 关键字
throw 关键字
throw 关键字用于在当前方法或者在catch代码块中手动抛出一个异常。
使用场景:自定义异常。。让报错信息更加有追踪性。。
代码:
public void checkNumber(int num) {
if (num < 0) {
throw new IllegalArgumentException("Number must be positive");
}
}
throws 关键字
throws 关键字用于在方法声明中指定该方法可能抛出的异常。当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。
例如,下面的代码中,当 readFile 方法内部发生 IOException 异常时,会将该异常传递给调用该方法的代码。在调用该方法的代码中,必须捕获或声明处理 IOException 异常。
代码:
public void readFile(String filePath) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
reader.close();
}
注意事项:一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
1.3 try-catch-finally
使用 try-catch-finally 手动关闭资源。
代码演示:
import java.io.*;j
class RunoobTest {
public static void main(String[] args) {
BufferedReader br = null;
String line;
try {
System.out.println("Entering try block");
br = new BufferedReader(new FileReader("test.txt"));
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
} finally {
System.out.println("Entering finally block");
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
System.out.println("IOException in finally block =>"+e.getMessage());
}
}
}
}
1.4 try-with-resources
try-with-resources 是一种异常处理机制,它能够自动关闭在 try 块中声明的资源,无需显式地在 finally 块中关闭。
在 try-with-resources 语句中,你只需要在 try 关键字后面声明资源,然后跟随一个代码块。无论代码块中的操作是否成功,资源都会在 try 代码块执行完毕后自动关闭。
语法:
try (resource declaration) {
// 使用的资源
} catch (ExceptionType e1) {
// 异常块
}
1.4.1 释放单个资源
演示优势:
使用 try-with-resources 自动关闭资源。
import java.io.*;
public class RunoobTest {
public static void main(String[] args) {
String line;
// 这里的会自动执行br.close()
try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
}
}
1.4.2 释放多个资源
try-with-resources 语句中可以声明多个资源,方法是使用分号 ; 分隔各个资源:
import java.io.*;
import java.util.*;
class RunoobTest {
public static void main(String[] args) throws IOException{
try (Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
while (scanner.hasNext()) {
writer.print(scanner.nextLine());
}
}
}
}
2.全局异常处理器
在 Java 中,特别是在 Spring Framework 中,全局异常处理器是一种用于集中处理应用程序中发生的异常的机制。这种处理器可以帮助你管理和处理应用程序中的异常,从而提供统一的错误处理逻辑和用户友好的错误响应。
举个例子:
在Java Web项目中,尤其是采用了经典的三层架构(Controller、Service、Mapper)的项目中,异常处理是一个非常重要的环节。通常,当业务操作数据库出错时,异常会从持久层(Mapper)向上抛出,经过Service层,最终到达Controller层。为了确保前端能够正确处理这些异常,并且避免出现500错误,通常需要定义全局异常处理器来集中处理异常。
因此我们都会封装一个Result类加上全局异常处理器,可以友好的响应数据给前端。
而且不会出现状态码500的情况。。
解决方案:
-
方案一:在所有Controller的所有方法中进行try…catch处理
-
缺点:代码臃肿(不推荐)
-
-
方案二:全局异常处理器
-
好处:简单、优雅(推荐)
-
2.1 全局异常处理器的实现
我们该怎么样定义全局异常处理器?
-
定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
-
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
注意事项:
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为json后再响应给前端。
在一个类上加@RestControllerAdvice就是全局异常处理器。
在异常处理器里面的方法加@ExceptionHandler就是处理的异常类型。此时就可以配合自定义异常使用。
代码演示:
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result ex(Exception ex){
ex.printStackTrace();//打印堆栈中的异常信息
return Result.error("出现错误信息,请解决");
}
//自定义异常(使报错更加详细清晰)
@ExceptionHandler(Custom.class)
public Result ee(Custom custom){
log.error(custom.getMessage());
return Result.error(custom.getMessage());
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler(Exception.class) //指定能够处理的异常类型
public Result ex(Exception e){
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error("对不起,操作失败,请联系管理员");
}
}
全局异常处理器捕获到异常就会返回Result对象错误信息,里面封装成JSON格式之后,会被可以
被前端解析,并且前端会给出友好的回应,这说明前端可以解析该JSON数据。
2.2 Result类封装
在 Spring Boot 项目中,通常使用一个自定义的 Result
类来封装响应数据。这样的封装可以帮助统一返回数据的格式,方便前端处理和调试。以下是一个常见的 Result
类封装示例:
package com.lhx.entity;
import java.io.Serializable;
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
private int code; // 响应码,用在error静态方法
private String message; // 响应消息,用在error静态方法
private T data; // 响应数据
// 构造函数
public Result() {
}
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// 成功响应,不带数据
// Result<T>这是方法的返回类型。
// <T>: 这是泛型参数,表示这个方法可以处理任意类型的数据。
// <T> Result<T>:第一个泛型是泛型方法,第二个泛型才可以使用到该泛型
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
// 成功响应,带数据
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
// 失败响应
public static <T> Result<T> error(int code, String message) {
return new Result<>(code, message, null);
}
// Getter 和 Setter 方法
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
3.自定义异常
在 Java 中,自定义异常是创建应用程序中用于特定错误处理的一种常见做法。
自定义异常可以提供更具体的错误信息,帮助在捕获和处理异常时进行更细粒度的控制。
3.1 自定义检查型异常实现
下面是一个自定义检查型异常的示例,继承自 Exception
:
// 自定义检查型异常
public class MyCheckedException extends Exception {
// 默认构造函数
public MyCheckedException() {
// 调用父类的无参构造器
super();
}
// 带有错误信息的构造函数
public MyCheckedException(String message) {
// 调用父类的有参构造器
super(message);
}
// 带有错误信息和异常原因的构造函数
public MyCheckedException(String message, Throwable cause) {
// 调用父类的有参构造器
super(message, cause);
}
// 带有异常原因的构造函数
public MyCheckedException(Throwable cause) {
// 调用父类的有参构造器
super(cause);
}
}
3.2 使用自定义检查型异常
在代码中使用自定义检查型异常时,需要在方法签名中声明抛出该异常,并在调用该方法时处理异常:
public class Example {
public void doSomething() throws MyCheckedException {
// 可能会抛出 MyCheckedException 的代码
throw new MyCheckedException("Something went wrong!");
}
public static void main(String[] args) {
Example example = new Example();
try {
example.doSomething();
} catch (MyCheckedException e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
}
3.3 自定义非检查型异常实现
下面是一个自定义非检查型异常的示例,继承自 RuntimeException
:
// 自定义非检查型异常
public class MyRuntimeException extends RuntimeException {
// 默认构造函数
public MyRuntimeException() {
super();
}
// 带有错误信息的构造函数
public MyRuntimeException(String message) {
super(message);
}
// 带有错误信息和异常原因的构造函数
public MyRuntimeException(String message, Throwable cause) {
super(message, cause);
}
// 带有异常原因的构造函数
public MyRuntimeException(Throwable cause) {
super(cause);
}
}
3.4 使用自定义非检查型异常
与检查型异常不同,非检查型异常不需要在方法签名中声明,可以直接抛出:
public class Example {
public void doSomething() {
// 可能会抛出 MyRuntimeException 的代码
throw new MyRuntimeException("Something went wrong!");
}
public static void main(String[] args) {
Example example = new Example();
try {
example.doSomething();
} catch (MyRuntimeException e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
}
通过定义继承自 Exception
或 RuntimeException
的类,你可以创建具有明确语义和额外上下文的异常,帮助你更好地管理和调试程序中的错误。
4.总结
全局异常处理器通常用于集中处理程序中可能抛出的所有异常,而自定义异常则用于定义和识别特定类型的错误情况。
从下面代码可以看出,基于web开发,接收到错误信息之后,返回给前端的是一个Result对象对应的JSON格式,该JSON可以被前端解析。如果没有进行统一对象处理,框架也会返回一个默认错误的JSON格式给前端,但是前端并解析不了这个默认JSON。
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result ex(Exception ex){
ex.printStackTrace();//打印堆栈中的异常信息
return Result.error("出现错误信息,请解决");
}
//自定义异常(使报错更加详细清晰)
@ExceptionHandler(Custom.class)
public Result ee(Custom custom){
log.error(custom.getMessage());
return Result.error(custom.getMessage());
}
}
public void logInfo(String useName,String passWord) throws UseNameException,PassWordException {
if(!this.useName.equals(useName)){
throw new UseNameException("用户名错误");
}
if(!this.passWord.equals(passWord)){
throw new PassWordException("密码错误");
}
System.out.println("登陆成功");
}