八股基础-开发框架
内容
Spring IOC
谈谈你对
Spring IOC
(控制反转)和DI
(依赖注入)的理解。IOC
(控制反转): 是一种设计思想,实际上就是将程序主动创建、管理对象的控制权交给Spring
容器统一管理。这样一来就不用关注对象创建管理流程,实现了对象之间的解耦。DI
(依赖注入): 是实现IOC
的一种方式。它是由容器在运行时动态地将依赖关系注入到组件之中。Spring
主要通过构建函数注入、Setter 方法注入和字段注入方式实现。
Spring 的
IOC
容器是如何工作的?IOC
容器工作流程/Bean
对象生命周期:- 资源定位(BeanDefinition 定位): 容器找到配置文件(
application.xml
)或注解(@Configration
),准备进行解析; - 加载解析(BeanDefinition 载入和解析): 将配置信息解析成
BeanDefinition
对象,这个对象定义了Bean
的所有元数据 (类名、作用域、属性值、初始化方法等)。BeanDefinition
是IOC
容器管理对象的基础。 - 注册(BeanDefinition 的注册): 解析得到的
BeanDefinition
会统一注册到BeanDefinitionRegistry
中,此时Bean
还没有被实例化。 Bean
的实例化 : 容器通过反射调用Bean
的构造函数来创建Bean
的实例。- 属性填充 : 容器将
Bean
定义的属性值(包括其他对Bean
的引用)注入到实例中 - 初始化 :
- 如果
Bean
实现了BeanNameAware
等Aware
接口,则调用回调方法。 - 执行
Bean
后置处理器(BeanPostProfessor
)的postProcessBeforeInitialization
方法 - 调用
Bean
指定的初始化方法(如init-method
或@PostConstruct
注解的方法) - 执行
Bean
后置处理器的postProcessAfterInitialization
方法(这里可能发生AOP
代理!)。
- 如果
- 使用 :
- 销毁 : 当容器关闭时,如果
Bean
实现了DisposableBean
接口或指定了销毁方法(destroy-method
或@PreDestroy
),则会调用相应的方法。
- 资源定位(BeanDefinition 定位): 容器找到配置文件(
BeanFactory
和ApplicationContext
有什么区别?BeanFactory
vsApplicationContext :
- BeanFactory(基础接口): 提供了 IOC 容器最基础的功能。它采用懒加载,只有在第一次通过 getBean() 请求时才会初始化 Bean
- ApplicationContext(高级接口,继承自 BeanFactory): 是 BeanFactory 的超集,提供更多功能
- 消息资源处理(i8n)
- 事件发布(ApplicationContext)
- 统一的资源文件访问方式(ResourceLoader)
- 载入多个(有继承联系)的上下文
- 默认立即初始化单例 Bean,这样就可以在启动时立即发现配置错误,而非运行时
一般都用后者
Spring AOP
- Spring AOP 的实现原理是什么?
- 实现原理 : 基于动态代理实现
- 如果一个类实现了接口,Spring 默认使用 JDK 动态代理来创建代理对象
- 如果没有实现任何接口,Spring 会使用 CGLIB 动态代理来创建代理对象
- 实现原理 : 基于动态代理实现
它和 AspectJ 有什么区别?
| 特性 | Spring AOP | AspectJ |
| —————— | ——————————————————————— | ———————————————————————— |
| 实现方式 | 运行时动态代理 | 编译时或类加载时织入 |
| 性能 | 运行时稍有性能开销 | 运行时无额外性能开销 |
| 能力 | 仅支持方法级别的切面 | 功能更强大,支持字段、构造器、方法等更多连接点 |
| 依赖 | 轻量,仅依赖于 Spring 容器 | 更重,需要额外的编译器和织入器 |
| 适用场景 | 满足大多数企业应用需求,解决声明式事务、日志等 | 需要更复杂的切面功能(修改字段值)的性能敏感场景 |JDK 动态代理和 CGLIB 动态代理有什么区别?
| 特性 | JDK | CGLIB |
| ———— | ———————————————————————— | ————————————————————————— |
| 机制 | 基于接口,通过反射实现了一个相同接口的代理类 | 基于继承,通过字节码技术生成目标类的子类作为代理类 |
| 限制 | 目标类必须实现至少一个接口 | 目标类不能是 final,方法不能是 final/static |
| 性能 | 在 JDK1.8+之后,生成代理对象和调用速度有显著提升 | 生成代理对象较慢,但调用速度通常更快 |Spring 如何选择?
- 默认策略:Spring 如果目标对象实现了接口,则使用 JDK 代理;否则使用 CGLIB。SpringBoot 默认使用 CGLIB。
- 可以通过配置 spring.aop.proxy-target-class=true 来强制使用 CGLIB 代理,即使有接口也会生成一个基于子类的代理。这在需要代理类而不是接口,或希望代理没有在接口中声明的方法时很有用。
Spring
循环依赖
什么是循环依赖?
- 循环依赖是指两个或多个 Bean 相互支持对方的引用,形成一个闭环
Spring 是如何解决循环依赖的?
通过三级缓存来提前暴露未完全初始化的 Bean 实例,从而解决循环依赖
- 一级缓存:存放已经完全初始化好的单例 Bean
- 二级缓存:存放提前暴露的早期 Bean,还没有进行属性填充和初始化
- 三级缓存:存放 Bean 的工厂对象
解决流程
- 创建 A,实例化 A,然后将 A 的工厂对象放入三级缓存
- 准备为 A 进行属性填充,发现需要注入 B
- 开始创建 B,实例化 B,然后将 B 的工厂对象放入三级缓存
- 准备为 B 进行属性填充,发现需要注入 A
- 从缓存中寻找
A
,依次检查三级缓存 1 -> 2 -> 3 - 在三级缓存中找到了 A 的工厂方法,调用
getObject()
方法。该方法执行可能存在的后置处理器,可能会返回一个早期对象。将这个早期对象放入二级缓存,并从三级缓存移除 - B 成功获取到 A 的早期应用,完成属性注入和初始化,成为完整 Bean,放入一级缓存。清理二三级缓存中 B 的信息
- A 紧接着注入初始化好的 B,继续 A 的初始化流程。。。
能解决所有情况的循环依赖吗?
- 不能解决所有循环依赖
- 只能解决 Setter 和字段注入,即属性注入发生在实例化之后
- 不能解决构造器注入,因为 JVM 要求在构造方法中必须完成所有依赖的初始化,而 Spring 在实例化 Bean(调用构造器)时就需要所有依赖项,此时 Bean 还完全不可用,无法提前暴露引用,导致死锁。
Spring
事务
Spring
事务的传播机制有哪些?- 传播机制(Propagation): 定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。
REQUIRED
(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。REQUIRES_NEW
: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。两个事务互不干扰。SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。NOT_SUPPORTED
: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。MANDATORY
: 必须在一个已有的事务中运行,否则抛出异常。NEVER
: 必须不在事务中运行,否则抛出异常。NESTED
: 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则行为同 REQUIRED。嵌套事务可以独立于外部事务进行提交或回滚(Savepoint 机制)。
- 传播机制(Propagation): 定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。
隔离级别呢?
- 隔离级别(Isolation): 与数据库隔离级别概念一致,Spring 只是做了一个抽象和传递。
DEFAULT
:使用底层数据库的默认隔离级别。READ_UNCOMMITTED
:读未提交。READ_COMMITTED
:读已提交(最常用)。REPEATABLE_READ
:可重复读。SERIALIZABLE
:串行化。
- 隔离级别(Isolation): 与数据库隔离级别概念一致,Spring 只是做了一个抽象和传递。
@Transactional
注解在什么情况下会失效?@Transactional
失效的常见场景:- 方法非
public
:@Transactional
只能用于public
方法上,否则事务不生效。 - 自调用(同一个类中): 在一个类中,一个非事务方法 A 调用同一个类里的事务方法 B,事务会失效。因为事务是基于
AOP
代理的,自调用不会经过代理对象。 - 异常被捕获(
catch
): 默认只在抛出运行时异常(RuntimeException
) 和 Error 时回滚。如果抛出了受检异常(Exception
)或者异常被方法内部catch
处理了,事务不会回滚。可以通过@Transactional(rollbackFor = Exception.class)
来指定回滚的异常类型。 - 数据库引擎不支持: 例如使用 MyISAM 引擎,它本身就不支持事务。
- 未开启事务管理(
@EnableTransactionManagement
): 在配置类上忘记添加此注解。
- 方法非