1. Java 中的异常分类
Java 中的异常分为以下三类:
-
受检异常(Checked Exception)
-
继承自
Exception
,但不包括RuntimeException
及其子类。 -
编译时需要处理(捕获或声明抛出)。
-
常见的受检异常:
IOException
、SQLException
、ClassNotFoundException
。 -
适用于程序可以合理预期并处理的异常场景。
-
例子:
try { FileReader reader = new FileReader("file.txt"); } catch (IOException e) { e.printStackTrace(); }
-
-
运行时异常(Runtime Exception)
-
继承自
RuntimeException
。 -
编译时不强制处理,程序运行时可能发生。
-
常见的运行时异常:
NullPointerException
、ArrayIndexOutOfBoundsException
、ArithmeticException
。 -
适用于程序逻辑中的错误,通常可以通过修改代码避免。
-
例子:
int result = 10 / 0; // 抛出 ArithmeticException
-
-
错误(Error)
- 继承自
Error
,表示严重的系统级错误,通常无法通过代码处理。 - 常见的错误:
OutOfMemoryError
、StackOverflowError
。 - 不建议捕获或处理,因为它们通常是不可恢复的。
- 继承自
2. Java 中的访问修饰符
Java 提供了四种访问修饰符来控制类、方法和变量的访问范围:
修饰符 | 同类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public |
✔ | ✔ | ✔ | ✔ |
protected |
✔ | ✔ | ✔ | ✘ |
默认(无修饰符) | ✔ | ✔ | ✘ | ✘ |
private |
✔ | ✘ | ✘ | ✘ |
public
:对所有类可见,无论位于哪个包中。protected
:对同一包中的类以及其他包中的子类可见。- 默认(无修饰符):对同一包中的类可见。
private
:仅对同一类中的成员可见。
注意:Java 中没有名为 default
的访问修饰符。当不指定修饰符时,采用默认的包级访问权限。
3. Spring Bean 的生命周期
Spring 管理的 Bean 在容器中有一系列生命周期过程:
- 实例化 Bean
- 通过构造函数创建 Bean 实例。
- 依赖注入
- 通过 setter 方法或注解(如
@Autowired
)进行依赖注入。
- 通过 setter 方法或注解(如
- 处理 Aware 接口
- 如果 Bean 实现了某些 Aware 接口(如
BeanNameAware
、ApplicationContextAware
),Spring 会在此阶段调用它们的方法,让 Bean 获取 Spring 容器相关信息。
- 如果 Bean 实现了某些 Aware 接口(如
- 执行 BeanPostProcessor 前置处理器
- 在 Bean 初始化方法之前调用,可以用于修改 Bean 实例。
- 调用初始化方法
- 如果 Bean 实现了
InitializingBean
接口或配置了自定义的init-method
,Spring 会调用这些方法进行初始化。
- 如果 Bean 实现了
- 执行 BeanPostProcessor 后置处理器
- 在初始化方法之后调用,可能在此阶段为 Bean 创建代理对象。
- 使用 Bean
- 销毁 Bean
- 如果实现了
DisposableBean
接口或配置了自定义destroy-method
,Spring 会在容器关闭时调用这些方法进行清理。
- 如果实现了
4. Aware 接口的作用
Spring 提供了一系列 \*Aware
接口,让 Bean 可以与 Spring 容器交互,获得容器的一些上下文信息。
常见的 Aware 接口包括:
-
BeanNameAware
:获取当前 Bean 的名称。public class MyBean implements BeanNameAware { @Override public void setBeanName(String name) { System.out.println("Bean name is: " + name); } }
-
ApplicationContextAware
:获取当前的ApplicationContext
实例。public class MyBean implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) { System.out.println("ApplicationContext set."); } }
-
EnvironmentAware
:获取当前环境配置信息(如环境变量和属性)。
5. 什么是 BeanDefinition?
BeanDefinition
是 Spring 用来描述 Bean 的定义信息的核心接口。它包含了关于 Bean 的所有元信息,如:
- Bean 的类名
- 作用域(
singleton
或prototype
) - 是否延迟加载(
lazy-init
) - 构造函数参数
- 属性值
- 初始化和销毁方法
Spring 容器在启动时,会根据配置文件或注解生成 BeanDefinition
,并在需要时根据这些定义信息创建和管理 Bean 实例。
示例:BeanDefinition 的常见来源
-
XML 配置文件
<bean id="myBean" class="com.example.MyClass" scope="singleton" lazy-init="true" />
-
Java 配置类
@Bean public MyClass myBean() { return new MyClass(); }
6. Spring 的事务管理
Spring 提供了强大的事务管理支持,主要有以下两种方式:
- 编程式事务管理
- 使用
TransactionTemplate
或底层事务 API 明确地在代码中管理事务。 - 优点:灵活性高。
- 缺点:对业务代码有侵入性,代码可读性差。
- 使用
- 声明式事务管理(推荐)
- 基于 AOP,通过注解(如
@Transactional
)声明事务规则,Spring 在运行时自动管理事务。 - 优点:代码简洁,易于维护。
- 缺点:灵活性稍差,但对于大多数场景已经足够。
- 基于 AOP,通过注解(如
事务失效场景:
- 方法内部捕获异常后不抛出,事务不会回滚。
- 如果 Service 方法抛出的异常在 Controller 层被全局异常处理器捕获,事务仍然有效,只要异常在事务边界内被抛出,Spring 就能检测到并触发回滚。
7. Service 层方法的访问修饰符
- 默认访问级别(无修饰符):只能在同一包中访问。
- 在 Spring 项目中,建议 Service 方法显式声明为
public
,以确保 Spring AOP 和事务管理能够正常工作。
8. 代理模式与 AOP
- 代理模式:通过代理对象控制对目标对象的访问,可以为目标对象添加额外功能。
- AOP(面向切面编程):用于将横切关注点(如日志、事务)与业务逻辑分离,常用代理模式来实现。
- Spring AOP 通过动态代理为目标对象创建代理,在方法调用前后插入切面逻辑。
9. Java 中的 BLOB 和 CLOB
- BLOB(Binary Large Object):用于存储大量二进制数据,如图片、音频或视频文件。在数据库中,BLOB 是一种字段类型,专门用于存储此类二进制文件。在 Java 中,可以使用
java.sql.Blob
类来处理 BLOB 数据。 - CLOB(Character Large Object):用于存储大量字符数据,如文本文件。在数据库中,CLOB 是一种字段类型,专门用于存储此类大文本数据。在 Java 中,可以使用
java.sql.Clob
类来处理 CLOB 数据。
10. 什么是版本链(Version Chain)?
在 InnoDB 存储引擎中,多版本并发控制(MVCC) 是通过 版本链 来实现的。
每行数据都包含多个版本,每个版本对应一次修改操作。版本链中的数据可以帮助实现事务隔离级别(如 READ COMMITTED
和 REPEATABLE READ
),从而避免幻读和脏读等问题。
版本链中的重要字段:
trx_id
(事务ID):表示创建或修改该版本的事务ID。undo log
:记录了每次数据更新前的旧值,构成一个链表,称为版本链。Roll Pointer
:指向上一个版本的数据,实现数据的版本回溯。
11. ReadView 介绍
ReadView 是在事务执行时生成的一种快照,它记录了在生成快照时,系统中哪些事务是活跃的(未提交)。ReadView 用于判断一个数据版本是否对当前事务可见。
ReadView 主要字段:
min_trx_id
:生成 ReadView 时,系统中活跃事务中最小的事务ID。max_trx_id
:生成 ReadView 时,当前系统中的下一个事务ID。m_ids
:表示生成 ReadView 时,系统中所有活跃事务的 ID 列表。
12. 版本链数据访问规则
当事务读取某行数据时,InnoDB 会根据 ReadView 的规则判断该行数据的版本是否对当前事务可见。判断规则如下:
trx_id < min_trx_id
- 当前数据版本在生成 ReadView 之前已经提交,可以访问。
- 这是最简单的情况,说明数据是历史版本,事务生成 ReadView 时已经存在且提交。
trx_id > max_trx_id
- 当前数据版本对应的事务在生成 ReadView 之后才开启,不可访问。
- 解释: 这是一个未开始或未提交的版本,当前事务不应看到它。
min_trx_id <= trx_id <= max_trx_id
- 如果
trx_id
在m_ids
列表中,说明该版本对应的事务仍未提交,不允许访问。 - 如果
trx_id
不在m_ids
中,说明事务已经提交,可以访问。
- 如果
13. 针对问题的详细解释
问题 1(第3条规则解释)
判断 trx_id > max_trx_id
,成立说明该事务是在 ReadView 生成以后才开启,不允许访问。
解释:
在事务生成 ReadView 的瞬间,max_trx_id
记录了当前系统中的下一个事务 ID。如果某个数据版本的 trx_id
大于 max_trx_id
,说明这个事务在 ReadView 生成后才开始,数据版本对当前事务不可见。
示例:
- 系统当前的事务情况:
min_trx_id = 2
max_trx_id = 5
m_ids = [2, 3, 4]
- 事务操作顺序:
- 事务 A 开始并生成 ReadView。
- 事务 B(
trx_id = 6
)在 ReadView 生成后开始并修改了某条数据。
- 判断数据版本的可见性:
- 如果数据版本的
trx_id = 6
,显然它大于max_trx_id = 5
,说明该数据版本是事务 B 创建的。 - 因为事务 B 在事务 A 的 ReadView 生成后才开启,因此事务 A 无法看到这个版本。
- 如果数据版本的
问题 2(第4条规则解释)
判断 min_trx_id <= trx_id <= max_trx_id
,如果 trx_id
不在 m_ids
列表中,代表数据是已提交的,可以访问。
解释:
在 ReadView 生成时,m_ids
记录了所有活跃事务的 ID。如果某个数据版本的 trx_id
在 m_ids
中,说明该事务仍未提交,当前事务无法访问。反之,如果 trx_id
不在 m_ids
中,说明事务已经提交,数据对当前事务可见。
示例:
- 系统当前的事务情况:
min_trx_id = 2
max_trx_id = 5
m_ids = [2, 3, 4]
- 事务操作顺序:
- 事务 A 开始并生成 ReadView。
- 事务 C(
trx_id = 5
)已经提交并修改了某条数据。
- 判断数据版本的可见性:
- 数据版本的
trx_id = 5
,它在min_trx_id
和max_trx_id
之间。 trx_id = 5
不在m_ids = [2, 3, 4]
中,说明事务 C 已经提交,数据版本对事务 A 可见。
- 数据版本的
问题 3:undolog 记录的时机
undo log 是在事务开始时就记录操作,还是等到实际操作时才记录?
解释:
undo log
是在 数据发生修改时 记录的,不是在事务开始时就立即生成。每当事务对数据进行修改,InnoDB 会将修改前的旧值写入 undo log
,从而形成一个回滚链条,方便事务回滚或多版本读取。