面试:Spring
1.依赖注入(IOC)高频问题解析
1.1 Spring的IOC是什么意思?
- 核心定义:
IOC(Inversion of Control,控制反转)是设计思想,将对象的 创建、依赖管理权限 从开发者转移到Spring容器。 - 实现方式(DI):
通过 依赖注入(Dependency Injection)完成,包括 构造器注入、Setter注入、字段注入(如@Autowired
)。 - 对比传统模式:
传统开发中,开发者需手动new
对象并维护依赖(如UserService service = new UserService(new UserDao())
);IOC下,容器统一管理Bean的生命周期和依赖,解耦对象关系。 - 总结:IOC让开发者聚焦业务,容器负责“对象编排”,是Spring解耦的核心。
1.2 Spring中的Bean是线程安全的吗?
- 分场景判断:
- 单例(默认):
- 若Bean是 无状态(无成员变量或变量只读),天然线程安全(如工具类
StringUtils
); - 若含 可变成员变量(如
private String name
),多线程共享会引发竞争(如并发修改name
)。 - 解决方法:
- 避免定义可变状态;
- 用
ThreadLocal
隔离线程变量; - 改为原型(prototype)(每次请求新建Bean,性能开销大);
- 加锁(
synchronized
,影响效率)。
- 若Bean是 无状态(无成员变量或变量只读),天然线程安全(如工具类
- 原型(prototype):每次获取新实例,无共享问题,线程安全。
- 单例(默认):
1.3 什么是Spring的三级缓存?
- 缓存结构(解决单例Bean循环依赖的核心):
缓存层级 作用 存储对象状态 一级缓存( singletonObjects
)存储 完全初始化完成 的单例Bean 成品Bean(可直接使用) 二级缓存( earlySingletonObjects
)存储 实例化后、未初始化 的半成品Bean 半成品(已实例化,未注入依赖/初始化) 三级缓存( singletonFactories
)存储 Bean的工厂函数(生成早期引用) 工厂Lambda(创建早期Bean) - 工作流程(以
A→B→A
循环依赖为例):- 创建
A
:实例化后,将A
的工厂放入三级缓存,尝试注入B
。 - 创建
B
:实例化后,从三级缓存取A
的早期引用(放入二级缓存,删除三级缓存),完成B
的初始化并放入一级缓存。 - 回到
A
:从二级缓存取B
的成品,完成A
的初始化,最终放入一级缓存。
- 创建
1.4 Spring的循环依赖问题是什么?如何解决?
- 问题定义:
多个Bean互相依赖(如A
依赖B
,B
依赖A
),导致创建时相互等待,无法初始化。 - 解决范围:
- ✅ 支持:Setter注入/字段注入(依赖注入发生在实例化后,可通过三级缓存提前暴露引用)。
- ❌ 不支持:构造器注入(依赖注入发生在实例化阶段,此时Bean未实例化,无法提前暴露),会抛
BeanCurrentlyInCreationException
。
- 解决原理:
利用三级缓存,在Bean 实例化后(未初始化) 提前暴露引用,打破循环等待。
2. AOP原理高频问题解析
2.1 SpringAOP的实现原理是什么?
- 核心:动态代理,运行时生成目标对象的代理类,拦截方法调用并增强。
- 两种代理方式:
- JDK动态代理:
- 基于接口(目标类必须实现接口),通过
java.lang.reflect.Proxy
生成代理,重写invoke
方法。 - 优点:JDK原生支持,性能高;缺点:必须依赖接口。
- 基于接口(目标类必须实现接口),通过
- CGLIB代理:
- 基于类继承(目标类无需接口),通过字节码增强生成子类,重写目标方法。
- 优点:支持类代理;缺点:无法代理
final
类/方法(子类无法继承)。
- JDK动态代理:
- Spring策略:
- 目标类有接口:优先JDK代理;
- 目标类无接口或配置强制CGLIB(如Spring Boot默认):用CGLIB。
- 织入时机:运行时(区别于AspectJ的编译时织入),代理对象替代目标对象执行,插入通知(Before、After等)。
2.2 Spring的AOP在什么场景下会失效?
常见失效场景:
- 目标对象未被Spring管理:
手动new
对象(如UserService service = new UserService()
),而非从容器获取,代理不生效。 - 方法是
final/static
:final
方法:CGLIB无法继承重写,JDK代理也无法拦截(接口方法不能是final
);static
方法:属于类,代理针对实例方法,无法拦截。
- 同一类内的方法调用(
this
调用):
如class A { public void method1() { this.method2(); } }
,method2
的切面不生效——因this
是目标对象,而非代理对象,跳过拦截。 - 代理方式不匹配:
目标类有接口,但强制用CGLIB代理,且方法是final
,则代理失败(CGLIB无法重写final
方法)。
3. Bean生命周期与创建高频问题解析
3.1说一下Spring Bean的生命周期是怎么样的?
单例Bean完整流程:
- 实例化(Instantiation):通过构造器/工厂方法创建Bean实例(内存分配,未注入依赖)。
- 属性注入(Populate):注入依赖(@Autowired、
<property>
等)。 - 初始化前(BeanPostProcessor前置处理):执行
BeanPostProcessor.postProcessBeforeInitialization
,可修改Bean属性(如统一加日志)。 - 初始化(Initialization):
- 实现
InitializingBean
:调用afterPropertiesSet
; - 配置
init-method
:执行自定义初始化逻辑。
- 实现
- 初始化后(BeanPostProcessor后置处理):执行
BeanPostProcessor.postProcessAfterInitialization
,此处是AOP生成代理的关键时机(替换目标对象为代理)。 - 使用(Usage):Bean进入容器,供其他Bean调用。
- 销毁(Destruction):容器关闭时,实现
DisposableBean
则调用destroy
,或执行destroy-method
。
3.2聊聊Spring的BeanFactory和FactoryBean?
- BeanFactory:
- 角色:Spring容器的顶层接口,定义IOC容器核心能力(如
getBean()
、containsBean()
),管理所有Bean的生命周期。 - 典型实现:
DefaultListableBeanFactory
(Spring内部核心容器)。
- 角色:Spring容器的顶层接口,定义IOC容器核心能力(如
- FactoryBean:
- 角色:特殊的Bean(由BeanFactory管理),作用是创建其他Bean(工厂模式)。
- 场景:复杂对象创建(如MyBatis的
SqlSessionFactoryBean
,构建SqlSessionFactory
)。 - 核心方法:
getObject()
:返回实际要创建的Bean;getObjectType()
:返回Bean类型;isSingleton()
:是否单例。
- 类比:BeanFactory是“容器管理者”,FactoryBean是“Bean的工厂工人”——前者管所有Bean,后者专门生产某类Bean。
面试答题技巧
- 分层拆解:如Bean生命周期分阶段,结合接口(
InitializingBean
)和配置(init-method
),体现细节。 - 对比强调:如
BeanFactory
vsFactoryBean
,从“角色”“作用”维度区分,避免混淆。 - 原理+场景:解释三级缓存时,结合“
A→B→A
循环依赖”讲流程;讲AOP失效时,举“this
调用”的代码例子。 - 避坑提示:如构造器注入的循环依赖无法解决,单例Bean的线程安全问题,体现思考深度。