当前位置: 首页 > article >正文

Java异常处理的全面指南

Java异常处理的全面指南

    • 一、Java异常的基础概念
      • 1.1 什么是异常
      • 1.2 异常类的层次结构
    • 二、Java异常的处理方式
      • 2.1 try-catch块
      • 2.2 throws关键字
      • 2.3 throw关键字
    • 三、自定义异常
      • 3.1 自定义受检异常
      • 3.2 自定义非受检异常
    • 四、Java异常处理的最佳实践
      • 4.1 捕获合适粒度的异常
      • 4.2 避免过度使用异常
      • 4.3 正确处理finally块
      • 4.4 记录异常信息
    • 总结

程序运行过程中难免会遭遇各种意外状况,比如文件读取失败、网络连接中断、数据格式错误等,这些意外若不妥善处理,可能导致程序崩溃或产生不可预知的结果。Java的异常处理机制,就像一位“守护者”,专门用于捕获、处理这些意外情况,保障程序的稳定性与健壮性。本文我将带你深入剖析Java异常处理的各个方面,从基础概念到高级应用,并结合丰富示例代码,帮你全面掌握这一重要技术。

一、Java异常的基础概念

1.1 什么是异常

异常(Exception)指的是程序在运行过程中出现的非正常情况。当Java程序遇到错误或意外事件时,会创建一个异常对象,并抛出该异常,这个过程称为“抛出异常”。如果程序中没有对异常进行处理,异常会沿着调用栈向上传递,最终导致程序终止,并在控制台输出异常堆栈信息。例如,当我们尝试访问数组越界时:

public class ArrayOutOfBoundsExample {public static void main(String[] args) {int[] array = {1, 2, 3};System.out.println(array[3]);}
}

运行上述代码,程序会抛出 ArrayIndexOutOfBoundsException 异常,控制台输出如下:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3at ArrayOutOfBoundsExample.main(ArrayOutOfBoundsExample.java:6)

异常堆栈信息清晰地显示了异常类型、异常发生的位置(ArrayOutOfBoundsExample.java:6)等关键信息,帮助开发者定位问题。

1.2 异常类的层次结构

Java中的所有异常类都继承自 java.lang.Throwable 类,它是异常体系的根类。Throwable 有两个直接子类:

  • Error:表示严重的系统错误,如 OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)等。这类错误通常是由于系统资源耗尽或底层硬件问题导致的,应用程序一般无法捕获和处理,只能通过优化代码、增加系统资源等方式预防。
  • Exception:表示程序运行过程中出现的可恢复的异常情况,又可细分为受检异常(Checked Exception)非受检异常(Unchecked Exception)
    • 受检异常:必须在方法声明中使用 throws 关键字声明,或者在方法体内使用 try-catch 块进行捕获处理。例如,FileNotFoundException(文件未找到异常)、IOException(输入输出异常)等。这类异常通常是由于外部环境因素导致的,如文件不存在、网络连接中断等,开发者需要显式处理以保证程序的正确性。
    • 非受检异常:无需在方法声明中显式声明,也不必强制捕获处理。它们通常是由于程序逻辑错误引起的,如 NullPointerException(空指针异常)、IllegalArgumentException(非法参数异常)等。虽然不强制处理,但为了提高程序的健壮性,建议在合适的地方进行捕获和处理。

异常类的层次结构可以用如下树形图表示:

Throwable
Error
Exception
CheckedException
UncheckedException
IOException
SQLException
RuntimeException
NullPointerException
ArrayIndexOutOfBoundsException

二、Java异常的处理方式

2.1 try-catch块

try-catch 块是Java中最常用的异常处理方式,用于捕获并处理异常。其基本语法结构如下:

try {// 可能会抛出异常的代码块
} catch (ExceptionType1 e1) {// 处理ExceptionType1类型异常的代码
} catch (ExceptionType2 e2) {// 处理ExceptionType2类型异常的代码
} finally {// 无论是否发生异常,都会执行的代码块(可选)
}
  • try:包含可能会抛出异常的代码。如果在 try 块中发生异常,程序会立即跳出 try 块,进入匹配的 catch 块进行处理。
  • catch:用于捕获并处理特定类型的异常。一个 try 块可以跟随多个 catch 块,分别处理不同类型的异常。catch 块中的参数 e 是捕获到的异常对象,可以通过该对象获取异常的详细信息,如调用 e.getMessage() 获取异常信息,e.printStackTrace() 打印异常堆栈信息。
  • finally:是可选的,无论 try 块中是否发生异常,也无论是否有 catch 块捕获到异常,finally 块中的代码都会被执行。通常用于释放资源,如关闭文件流、数据库连接等。

以下是一个使用 try-catch 处理文件读取异常的示例:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;public class FileReadExample {public static void main(String[] args) {BufferedReader reader = null;try {reader = new BufferedReader(new FileReader("test.txt"));String line;while ((line = reader.readLine()) != null) {System.out.println(line);}} catch (IOException e) {System.out.println("文件读取失败: " + e.getMessage());e.printStackTrace();} finally {if (reader != null) {try {reader.close();} catch (IOException e) {System.out.println("关闭文件流失败: " + e.getMessage());}}}}
}

在上述代码中,try 块尝试读取文件内容,如果发生 IOException(如文件不存在、权限不足等),则会进入 catch 块进行处理,打印错误信息和堆栈跟踪。最后,在 finally 块中关闭文件流,确保资源被正确释放。

2.2 throws关键字

throws 关键字用于在方法声明中指出该方法可能抛出的异常类型。当一个方法内部无法处理某些异常时,可以将异常向上抛出,交给调用该方法的上层方法来处理。其语法格式如下:

public void methodName() throws ExceptionType1, ExceptionType2 {// 方法体,可能会抛出ExceptionType1或ExceptionType2类型的异常
}

例如,自定义一个方法读取文件内容,并将可能出现的 IOException 抛出:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;public class ThrowsExample {public static String readFileContent(String filePath) throws IOException {BufferedReader reader = new BufferedReader(new FileReader(filePath));StringBuilder content = new StringBuilder();String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}reader.close();return content.toString();}public static void main(String[] args) {try {String content = readFileContent("test.txt");System.out.println(content);} catch (IOException e) {System.out.println("读取文件时发生异常: " + e.getMessage());}}
}

readFileContent 方法中,由于可能会抛出 IOException,所以在方法声明中使用 throws IOException 进行声明。在 main 方法中调用该方法时,必须使用 try-catch 块捕获处理,或者继续向上抛出给更上层的调用者处理。

2.3 throw关键字

throw 关键字用于在程序中手动抛出一个异常对象。通常在自定义异常或者需要在特定条件下终止程序执行时使用。例如,自定义一个表示年龄不合法的异常类:

class InvalidAgeException extends RuntimeException {public InvalidAgeException(String message) {super(message);}
}public class ThrowExample {public static void checkAge(int age) {if (age < 0 || age > 150) {throw new InvalidAgeException("年龄不合法,范围应在0到150之间");}System.out.println("年龄合法");}public static void main(String[] args) {try {checkAge(-5);} catch (InvalidAgeException e) {System.out.println("捕获到异常: " + e.getMessage());}}
}

checkAge 方法中,当传入的年龄不满足条件时,使用 throw 手动抛出 InvalidAgeException 异常对象。在 main 方法中通过 try-catch 块捕获并处理该异常。

三、自定义异常

在实际开发中,Java提供的内置异常类可能无法满足所有业务需求。这时,我们可以自定义异常类,以便更准确地描述和处理特定的业务异常情况。自定义异常类通常继承自 Exception(用于受检异常)或 RuntimeException(用于非受检异常)。

3.1 自定义受检异常

以银行转账业务为例,当账户余额不足时,抛出一个自定义的受检异常 InsufficientBalanceException

import java.io.Serializable;class InsufficientBalanceException extends Exception implements Serializable {public InsufficientBalanceException(String message) {super(message);}
}class BankAccount {private double balance;public BankAccount(double initialBalance) {this.balance = initialBalance;}public void transfer(double amount, BankAccount targetAccount) throws InsufficientBalanceException {if (amount > balance) {throw new InsufficientBalanceException("余额不足,无法完成转账");}this.balance -= amount;targetAccount.balance += amount;System.out.println("转账成功");}
}public class CustomCheckedExceptionExample {public static void main(String[] args) {BankAccount account1 = new BankAccount(1000);BankAccount account2 = new BankAccount(500);try {account1.transfer(1500, account2);} catch (InsufficientBalanceException e) {System.out.println("转账失败: " + e.getMessage());}}
}

在上述代码中,InsufficientBalanceException 继承自 Exception,属于受检异常。在 transfer 方法中,当余额不足时抛出该异常,调用 transfer 方法的 main 方法必须使用 try-catch 块捕获处理,或者继续向上抛出。

3.2 自定义非受检异常

假设在一个电商系统中,当用户输入的商品数量为负数时,抛出一个自定义的非受检异常 InvalidQuantityException

class InvalidQuantityException extends RuntimeException {public InvalidQuantityException(String message) {super(message);}
}class Product {private String name;public Product(String name) {this.name = name;}public void purchase(int quantity) {if (quantity < 0) {throw new InvalidQuantityException("商品数量不能为负数");}System.out.println("购买了 " + quantity + " 件 " + name);}
}public class CustomUncheckedExceptionExample {public static void main(String[] args) {Product product = new Product("手机");try {product.purchase(-2);} catch (InvalidQuantityException e) {System.out.println("购买失败: " + e.getMessage());}}
}

InvalidQuantityException 继承自 RuntimeException,属于非受检异常。虽然在 main 方法中使用 try-catch 块捕获处理了该异常,但即使不捕获,程序也不会出现编译错误,不过为了提高程序的健壮性,建议进行捕获处理。

四、Java异常处理的最佳实践

4.1 捕获合适粒度的异常

在使用 try-catch 块时,应尽量捕获具体的异常类型,而不是宽泛地捕获 Exception 类。这样可以更准确地处理不同类型的异常,避免掩盖真正的问题。例如:

try {// 代码逻辑
} catch (NullPointerException e) {// 处理空指针异常的逻辑
} catch (IOException e) {// 处理输入输出异常的逻辑
}

而不是写成:

try {// 代码逻辑
} catch (Exception e) {// 处理所有异常的逻辑,这种方式可能会隐藏具体的异常信息
}

4.2 避免过度使用异常

异常机制主要用于处理非正常情况,而不是作为正常的程序流程控制手段。频繁地抛出和捕获异常会带来一定的性能开销,并且会使代码的可读性变差。例如,不要使用异常来判断一个条件是否满足,而应该使用条件语句进行正常的逻辑判断。

4.3 正确处理finally块

finally 块用于释放资源,但在编写 finally 块时要注意,其中的代码也可能会抛出异常。如果 try 块和 finally 块都抛出异常,finally 块中的异常会覆盖 try 块中的异常,导致真正的问题被掩盖。因此,在 finally 块中应尽量避免抛出新的异常,或者对可能抛出的异常进行妥善处理。

4.4 记录异常信息

在捕获异常时,除了打印异常堆栈信息外,建议使用日志框架(如Log4j、Logback)记录异常信息。这样可以方便在生产环境中排查问题,并且可以设置不同的日志级别,灵活控制日志的输出。例如,使用Logback记录异常信息:

<configuration><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="debug"><appender-ref ref="CONSOLE" /></root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class LogbackExample {private static final Logger logger = LoggerFactory.getLogger(LogbackExample.class);public static void main(String[] args) {try {// 可能抛出异常的代码} catch (Exception e) {logger.error("发生异常", e);}}
}

总结

Java的异常处理机制是保障程序稳定运行的重要手段,通过合理地使用 try-catch 块、throwsthrow 关键字,以及自定义异常类,开发者可以有效地捕获、处理各种异常情况,同时遵循异常处理的最佳实践,能够提高代码的质量和可维护性。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

相关文章:

Java异常处理的全面指南

Java异常处理的全面指南 一、Java异常的基础概念1.1 什么是异常1.2 异常类的层次结构 二、Java异常的处理方式2.1 try-catch块2.2 throws关键字2.3 throw关键字 三、自定义异常3.1 自定义受检异常3.2 自定义非受检异常 四、Java异常处理的最佳实践4.1 捕获合适粒度的异常4.2 避…...

sql知识梳理(超全,超详细,自用)

目录 通识 查询的基本语法 数据库&#xff08;database&#xff09;操作 表&#xff08;table&#xff09;的操作 表中列的操作 索引操作 表中行的操作 insert into语句 update语句 删除语句 select语句 表与表之间的关系 连接查询 子查询 视图 数据备份与还原 …...

[ Qt ] | QPushButton常见用法

目录 绑定键盘快捷键 前面已经说了很多用法了&#xff0c;下面主要说说绑定键盘&#xff0c;设置Icon图片。 绑定键盘快捷键 实现四个按钮&#xff0c;可以使用wsad来控制另一个按钮的上下左右的移动。 #include "widget.h" #include "ui_widget.h"Wid…...

WEB3——为什么做NFT铸造平台?

相必之前看过我的入门项目推荐关于简易NFT铸造平台的文章。会有一些疑惑 WEB3—— 简易NFT铸造平台&#xff08;ERC-721&#xff09;-入门项目推荐-CSDN博客 WEB3&#xff0c;我直接在https://nft.storage网站里上传图片不行吗&#xff0c;必须用合约铸造NFT&#xff1f; 我做…...

电脑驱动程序更新工具, 3DP Chip 中文绿色版,一键更新驱动!

介绍 3DP Chip 是一款免费的驱动程序更新工具&#xff0c;可以帮助用户快速、方便地识别和更新计算机硬件驱动程序。 驱动程序更新工具下载 https://pan.quark.cn/s/98895d47f57c 软件截图 软件特点 简单易用&#xff1a;用户界面简洁明了&#xff0c;操作方便&#xff0c;…...

【机器学习基础】机器学习入门核心:数学基础与Python科学计算库

机器学习入门核心&#xff1a;数学基础与Python科学计算库 一、核心数学基础回顾1. 函数与导数2. Taylor公式3. 概率论基础4. 统计量5. 重要定理6. 最大似然估计&#xff08;MLE&#xff09;7. 线性代数 二、Python科学计算库精要1. NumPy&#xff1a;数值计算核心2. SciPy&…...

上交具身机器人的视觉运动导航!HTSCN:融合空间记忆与语义推理认知的导航策略

作者&#xff1a;Qiming Liu 1 ^{1} 1, Guangzhan Wang 2 ^{2} 2, Zhe Liu 3 , 4 ^{3,4} 3,4 and Hesheng Wang 1 , 3 , 5 , 6 ^{1,3,5,6} 1,3,5,6单位&#xff1a; 1 ^{1} 1上海交通大学自动化系&#xff0c; 2 ^{2} 2上海交通大学软件学院&#xff0c; 3 ^{3} 3上海交通大学教…...

【C++并发编程01】初识C++并发编程

1、并发是什么 并发是指两个或更多独立的活动同时发生&#xff0c;现实生活中常见的并发场景如边吃饭边看手机。 1.1、计算机中的并发&#xff1a; 计算机领域的并发是指在单个系统里同时执行多个独立的任务&#xff0c;而非顺序的进行一些活动。 我们在电脑上能够边听音乐边和…...

Mysql库的操作和表的操作

Mysql库和表的操作 库的操作1.查看数据库列表2.创建数据库3.使用数据库4.查看当前在那个数据库中5.显示数据库的创建语句6.修改数据库7.删除数据库8.备份和恢复数据库9.查看数据的连接情况(简单来说就是查看有多少人使用你的数据库) 表的操作1.创建表2.查看表结构3.修改表本身(…...

LangChain-结合GLM+SQL+函数调用实现数据库查询(三)

针对 LangChain-结合GLM+SQL+函数调用实现数据库查询(二)-CSDN博客 进一步简化 通过 LangChain 和大语言模型(GLM-4)实现了一个 AI 代理,能够根据自然语言提问自动生成 SQL 查询语句,并连接 MySQL 数据库执行查询,最终返回结果。 整个流程如下: 用户提问 → AI 生成 SQ…...

word文档格式规范(论文格式规范、word格式、论文格式、文章格式、格式prompt)

文章目录 prompt prompt [格式要求] - 字体&#xff1a;中文宋体小四&#xff1b;英文Times New Roman 12pt&#xff1b;标题黑体 - 行距&#xff1a;1.5倍&#xff08;段前段后0行&#xff09; - 边距&#xff1a;A4默认&#xff08;上下2.54cm&#xff0c;左右3.17cm&…...

Ubuntu 桌面版忘记账户密码的重置方法

如果你忘记了 Ubuntu 桌面版的用户密码&#xff0c;可以通过进入恢复模式&#xff08;Recovery Mode&#xff09;来重置密码。以下是详细步骤&#xff1a; 一、进入 GRUB 引导菜单 重启计算机&#xff1a;点击关机按钮&#xff0c;选择重启。在启动时按住 Shift 键&#xff1…...

抖音商城抓包 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 抓包展示 总结 1.出于安全考虑,本章未…...

[SC]sc_signal_rv的用法和sc_signal相比有什么优势?

sc_signal_rv的用法和sc_signal相比有什么优势? 在 SystemC 中,sc_signal<T> 是最常用的单驱动(single‐driver)信号通道;而 sc_signal_rv<W>(“rv” = resolved vector)则是一种多驱动、带总线(tri-state)分辨功能的信号。下面分几点来说明它们的…...

掌握 FreeRTOS:打造高效嵌入式系统的第一步

实例对比说明&#xff1a; 手机: 点击相机 -> 操作系统 -> 打开摄像头 无操作系统: 相机 -> 打开摄像头也能实现&#xff0c;但方式死板、不支持第三方应用 MCU 对比说明&#xff1a; 裸机开发: MCU -> 直接控制硬件 使用操作系统: MCU -> 操作系统 -> 硬…...

性能优化 - 案例篇:数据一致性

文章目录 Pre引言1. 分布式缓存概念2. Redis 与 Memcached 区别概览3. Spring Boot 中使用 Redis3.1 引入依赖与常用客户端3.2 RedisTemplate 的基本用法3.3 Spring Cache 注解式缓存 4. 秒杀业务简介及挑战5. Lua 脚本实现原子库存扣减5.1 准备阶段&#xff1a;数据预加载5.2 …...

Spring框架学习day6--事务管理

Spring事务管理 Spring事务管理是在AOP的基础上&#xff0c;当我们的方法完全执行成功后&#xff0c;再提交事务&#xff0c;如果方法中有异常&#xff0c;就不提交事务 Spring中的事务管理有两种方式&#xff1a; ​ 1.编程式事务 ​ 需要我们在业务代码中手动提交 ​ 2.声明式…...

免费酒店管理系统+餐饮系统+小程序点餐——仙盟创梦IDE

酒店系统主屏幕 房间管理 酒店管理系统的房间管理&#xff0c;可实现对酒店所有房间的实时掌控。它能清晰显示房间状态&#xff0c;如已预订、已入住、空闲等&#xff0c;便于高效安排入住与退房&#xff0c;合理分配资源&#xff0c;提升服务效率&#xff0c;保障酒店运营有条…...

Git企业级项目管理实战

目录 1. 准备工作 2. 添加成员 2.1 添加企业成员 2.2 添加项目成员 2.3 添加仓库开发人员 3. 开发场景 - 基于git flow模型的实践 3.1 新需求加入 3.2 修复测试环境 Bug 3.3 修改预发布环境Bug 3.4 修改正式环境 Bug 3.5 紧急修复正式环境 Bug 4. 拓展阅读 4.1 其…...

【实例】事业单位学习平台自动化操作

目录 一、创作背景: 二、实现逻辑: 三、代码分析【Deepseek分析】: 1) 主要功能 2)核心组件 2.1 GUI界面 (AutomationApp类) 2.2 浏览器自动化 2.3 平台特定处理 3) 关键技术 4)代码亮点 5)总结 四、运行截图: 五、程序代码: 特别声明:***本代码仅限编程学…...

4.8.3 利用SparkSQL统计每日新增用户

在本次实战中&#xff0c;我们的任务是利用Spark SQL统计每日新增用户数。首先&#xff0c;我们准备了用户访问历史数据&#xff0c;并将其上传至HDFS。然后&#xff0c;通过Spark的交互式编程环境&#xff0c;我们读取了用户文件并将其转换为结构化的DataFrame。接着&#xff…...

创建ipv6 only和ipv6+ip4的k8s集群的注意事项

关键字 : CNI calico vxlan flannel ipv6-only ipv6ipv4 在搭建ipv6-only或ipv6ipv4的k8s集群时&#xff0c;在worker节点加入集群后&#xff0c;发现worker节点上的CNI启动失败。 以下是calico的启动失败情况 : kubectl get pod -A输出如下 : NAMESPACE NAME …...

Qt概述:基础组件的使用

1. Qt框架简介 Qt是一个跨平台的C图形用户界面应用程序开发框架&#xff0c;它包含了丰富的GUI组件和强大的功能库。本次示例代码展示了Qt的几个核心概念&#xff1a; QMainWindow&#xff1a;主窗口类&#xff0c;提供标准的应用程序框架**信号与槽**机制&#xff1a;Qt的核…...

判断使用什么技术来爬取数据详细讲解

判断目标网站使用哪种数据加载形式是爬虫开发的第一步&#xff0c;也是最关键的一步。以下是系统化的诊断方法和步骤&#xff1a; 核心诊断流程 (使用浏览器开发者工具 - Chrome/Firefox为例) 初始观察 (肉眼判断) 页面加载后数据是否立刻可见&#xff1f; 是 → 可能是静态HTM…...

YOLOV7改进之融合深浅下采样模块(DSD Module)和轻量特征融合模块(LFI Module)

目录 一、研究背景​ 二. 核心创新点​ ​2.1 避免高MAC操作​ ​2.2 DSDM-LFIM主干网络​ 2.3 P2小目标检测分支​ ​3. 代码复现指南​ 环境配置 关键修改点 ​4. 实验结果对比​ 4.1 VisDrone数据集性能 4.2 边缘设备部署 4.3 检测效果可视化 ​5. 应用场景​ …...

【仿生机器人】仿生机器人认知-情感系统架构设计报告

来自 gemini 2.5 1. 执行摘要 本报告旨在为仿生机器人头部设计一个全面的认知-情感软件架构&#xff0c;以实现自然、情感智能的互动。拟议的架构将使机器人能够像人类一样&#xff0c;动态生成情绪、进行复杂的表情表达&#xff08;包括情绪掩饰&#xff09;、拥有强大的记忆…...

数学建模期末速成 多目标规划

内容整理自2-6-2 运筹优化类-多目标规划模型Python版讲解_哔哩哔哩_bilibili 求有效解的几种常用方法 线性加权法√ 根据目标的重要性确定一个权重&#xff0c;以目标函数的加权平均值为评价函数&#xff0c;使其达到最优。ɛ约束法 根据决策者的偏好&#xff0c;选择一个主要…...

常见ADB指令

目录 1. 设备连接与管理 2. 应用管理 3. 文件操作 4. 日志与调试 5. 屏幕与输入控制 6. 高级操作&#xff08;需Root权限&#xff09; 7. 无线调试&#xff08;无需USB线&#xff09; 常用组合示例 注意事项 以下是一些常用的 ADB&#xff08;Android Debug Bridge&a…...

IoTGateway项目生成Api并通过swagger和Postman调用

IoTGateway项目生成Api并通过swagger和Postman调用-CSDN博客...

sl4j+log4j日志框架

sl4jlog4j日志框架 slf4j (Simple Loging Facade For Java) 即它仅仅是一个为 Java 程序提供日志输出的统一接口&#xff0c;并不是一个具体的日志实现方案&#xff0c;所以单独的 slf4j 是不能工作的&#xff0c;必须搭配其他具体的日志实现方案&#xff08;例如&#xff1a;…...