Spring 面试回答
本文把 Spring 面试高频考点拆成六个子主题——IOC、AOP、Bean 生命周期、循环依赖、事务失效、自动装配,每个主题都按「三版本」展开:一句话结论(30s)、核心原理(2min)、底层深入(5-10min),末尾附 10 个追问点清单。
① IOC(控制反转)
一句话结论(30s)
IoC 就是把「对象的创建和依赖管理」从业务代码反转到容器——由 Spring 统一 new 对象、管生命周期、注入依赖。因为对象之间的依赖关系一旦变复杂,自己 new 会让改动一个依赖就牵连所有调用方;交给容器后只用注解声明依赖,改一处即可,还能天然拿到单例、懒加载和 AOP。
核心原理(2min)
先想:IoC 到底「反转」了什么?——反转的是「谁创建、谁管理依赖」的主动权:原来是「谁用谁 new」,现在是「容器建好给你」,下面的每个概念都是这条主线的延伸。
- IoC 是思想,DI(依赖注入)是手段:传统
new是「谁用谁建」的正转,Spring 反转为「容器建好给你」。 - 两大核心接口:
BeanFactory:最基础的容器,懒加载,getBean()时才创建。ApplicationContext:继承BeanFactory,启动时预实例化单例,额外提供事件发布、国际化、资源加载、AOP。
- 启动流程:解析配置(XML / 注解 / JavaConfig)→ 生成
BeanDefinition注册 →BeanFactoryPostProcessor改元数据 → 实例化单例(反射 new →populateBean注入 → init → 代理)。 - 三种注入:构造器注入(推荐,字段可
final、必填明确)、setter 注入(可选依赖)、字段注入(不推荐,难测试、隐藏依赖)。 - 收益:解耦、易测试、单例复用,是声明式事务和 AOP 的基石。
底层深入(5-10min)
BeanDefinition 是「食谱」:先想:你只写了一个 @Component,Spring 凭什么就帮你把对象建好了?——它不直接 new,而是先登记「食谱」:beanClassName / scope / lazyInit / initMethodName / propertyValues。扫描到 @Component 只生成定义存注册表,还没实例化——注册表是「待办清单」。
BFPP vs BPP——阶段分离的核心:
| BeanFactoryPostProcessor | BeanPostProcessor | |
|---|---|---|
| 作用对象 | BeanDefinition(元数据) | Bean 实例(对象) |
| 时机 | 实例化之前 | 实例化之后 |
| 典型实现 | PropertySourcesPlaceholderConfigurer(解析 ${}) | AutowiredAnnotationBeanPostProcessor |
BFPP 在食谱层改配方,BPP 在烹饪时下调味料。
FactoryBean:先想:有的 Bean 创建过程很复杂(要解析 XML、建连接池、跑一堆初始化),难道全塞进构造器?——于是有了 FactoryBean:工厂本身是一个 Bean,但 getBean("xxx") 拿到的是工厂生产的产品;&xxx 前缀(BeanFactoryUtils.FACTORY_BEAN_PREFIX = "&")才拿到工厂本身。例:MyBatis 的 SqlSessionFactoryBean——把「解析 XML + 构建 SqlSessionFactory」封装进 getObject()。
容器启动是模板方法:先想:容器启动要做的几十件事,顺序能乱吗?——不能,所以用模板方法定死骨架:AbstractApplicationContext.refresh() 固定 14 步顺序(obtainFreshBeanFactory → invokeBeanFactoryPostProcessors → registerBeanPostProcessors → finishBeanFactoryInitialization),子类 AnnotationConfigApplicationContext 只重写个别步骤。
doCreateBean——「容器建好给你」的落地代码:上面讲的都是「流程」,落到单个 Bean 上就是 AbstractAutowireCapableBeanFactory.doCreateBean 这段真实源码:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object @Nullable [] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.markAsPostProcessed();
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
throw bce;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
}
// ...(省略 earlySingletonExposure 二次校验与 registerDisposableBeanIfNecessary)
return exposedObject;
}
这段代码就是「容器建好给你」的落地:先 createBeanInstance 反射 new 出原始对象,再 populateBean 注入依赖、initializeBean 走初始化。注意 addSingletonFactory(beanName, () -> getEarlyBeanReference(...)) 这一行——它把半成品包进 ObjectFactory 塞进三级缓存,正是第④节循环依赖破局的起点;可见 IOC 的创建流程里天然就埋着循环依赖的解法。
② AOP(JDK 代理 vs CGLIB)
一句话结论(30s)
AOP 把事务、日志、权限这类横切逻辑从业务代码里抽出来,在方法调用前后动态织入。因为横切逻辑混在业务里会造成重复和侵入;实现靠动态代理——JDK 代理基于接口 + 反射(慢),CGLIB 基于子类 + FastClass 索引(快),Spring Boot 2.x 起强制默认 CGLIB。
核心原理(2min)
先想:日志、事务、权限这类逻辑横在几十个方法里、到处复制粘贴,怎么抽出来还不侵入业务?——答案就是「动态代理织入」,下面每个术语都是它的零件。
- 术语:切面 Aspect、连接点 JoinPoint、切入点 Pointcut、通知 Advice、织入 Weaving。
- 通知类型:
@Before/@After/@AfterReturning/@AfterThrowing/@Around。 - 两种代理对比:
| JDK 动态代理 | CGLIB | |
|---|---|---|
| 生成方式 | Proxy.newProxyInstance() | ASM 生成子类字节码 |
| 前提 | 需要接口 | 不需要接口 |
| 限制 | 只能代理接口方法 | final 类/方法不可代理 |
| 方法调用 | 反射 Method.invoke() | FastClass 索引(非反射) |
| 性能 | 慢 2-3 倍 | 快 |
- 默认策略:Spring 有接口用 JDK、无接口用 CGLIB;Spring Boot 2.x 起
proxyTargetClass默认 true → 全 CGLIB。
底层深入(5-10min)
JDK 代理两层反射开销:先想:代理无非就是「转发一次」,为什么还有快慢之分?——差别在转发的方式:生成的 $Proxy0 extends Proxy implements UserService,每次调用 super.h.invoke(this, m3, args) → InvocationHandler.invoke() → method.invoke(target, args),两层反射。
JdkDynamicAopProxy.invoke——代理转发的真实入口(org.springframework.aop.framework.JdkDynamicAopProxy):
@Override
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
// ...(省略 DecoratingProxy / Advised 等特殊分发分支)
Object retVal;
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fall back on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
@Nullable Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// ...(省略返回值修正逻辑)
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
// ...(省略 AopContext 代理上下文还原)
}
}
这就是 JDK 代理转发的真实入口:每次方法调用都进到这里,先拿 TargetSource 里的目标对象,再 getInterceptorsAndDynamicInterceptionAdvice 组装拦截器链。chain 为空就直接反射调目标(无增强),非空才 new 一个 ReflectiveMethodInvocation 并 proceed() 依次穿过 @Before/@Around/TransactionInterceptor 等通知——@Transactional 正是挂在链上的一环,绕开这里就等于绕开了事务。
CGLIB 为什么快:UserService$$EnhancerByCGLIB extends UserService,持有 MethodInterceptor,通过 MethodProxy 调用。CGLIB 为代理类和目标类各生成一个 FastClass(方法 → 固定 index 的索引数组),invokeSuper(proxy, args) 按 index 直接调用,没有反射开销。
为什么用 invokeSuper 而不是 method.invoke:先想:method.invoke(proxy, args) 这行看起来没毛病,怎么就会死循环?——因为 proxy 是代理子类,会再次触发拦截:
// 错误: method.invoke(proxy, args)
// proxy 是代理子类 → 又调子类重写的 sayHello() → 内部又调 interceptor.intercept()
// → 无限递归!
// 正确: methodProxy.invokeSuper(proxy, args)
// → 直接调父类原始实现 → 只织入一次, 无递归
为什么 Boot 2.x 强制 CGLIB:先想:把字段声明成接口类型不就能用 JDK 代理了吗,为什么还要强制 CGLIB?——因为大家习惯字段注入时声明成实现类:@Autowired UserServiceImpl 注入的是实现类类型,JDK 代理只实现了接口、不是实现类实例,无法匹配——CGLIB 生成的子类才是 UserServiceImpl 实例。
代理 vs 装饰器:结构几乎一样,区别在意图。代理控制访问(调用方不知情,透明替换),装饰器扩展功能(调用方显式包装)。Spring AOP 是代理,Java IO(new BufferedInputStream(new FileInputStream()))是装饰器。
注解本质也是 JDK 动态代理——
@interface是继承Annotation的接口,AnnotationInvocationHandler.invoke()不调method.invoke()而是按方法名从 class 文件RuntimeVisibleAnnotations解析出的 Map 取值。
③ Bean 生命周期
一句话结论(30s)
Bean 生命周期是一条可扩展的流水线:实例化 → 属性填充 → 初始化 → 使用 → 销毁。因为 Spring 靠 BeanPostProcessor 在初始化前后插桩,@Autowired、@PostConstruct、AOP 代理都是这条链上的一环——理解了这条链就理解了 Spring 的全部扩展机制。
核心原理(2min)
先想:一个 Bean 从无到有、再到被销毁,要经过哪几步?——抓住「实例化 → 注入 → 初始化 → 使用 → 销毁」这条主线,Spring 所有扩展点都是这条线上的插桩。背下这条链(singleton 主流程):
1. 实例化 createBeanInstance(反射 new 原始对象)
2. 属性填充 populateBean(@Autowired / @Value)
3. Aware 回调(BeanNameAware → BeanFactoryAware → ApplicationContextAware)
4. BeanPostProcessor.postProcessBeforeInitialization ← @PostConstruct 在这里
5. InitializingBean.afterPropertiesSet()
6. 自定义 init-method
7. BeanPostProcessor.postProcessAfterInitialization ← AOP 代理在这里生成
8. 使用
9. 销毁(@PreDestroy → DisposableBean.destroy() → destroy-method)
底层深入(5-10min)
阶段分离:先想:@Autowired(注入)、@Transactional(事务)、@Async(异步)三个八竿子打不着的功能,凭什么共用一套扩展机制?——因为都是挂在不同阶段的 BPP。BeanDefinition 阶段(BFPP)管「元数据」,实例化 + 初始化的 BPP 链管「实例对象」。这让 Spring 不改核心代码就能无限扩展——@Autowired、@Transactional、@Async 都只是不同的 BPP 实现,各自独立。
initializeBean——生命周期主链的真实源码(AbstractAutowireCapableBeanFactory):
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// Skip initialization of a NullBean
if (bean.getClass() == NullBean.class) {
return bean;
}
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
这段代码精确对应上面「核心原理」列出的第 3~7 步:invokeAwareMethods 回调 Aware,applyBeanPostProcessorsBeforeInitialization 里跑 @PostConstruct(CommonAnnotationBeanPostProcessor),invokeInitMethods 里先 afterPropertiesSet() 再自定义 init-method,最后 applyBeanPostProcessorsAfterInitialization 生成 AOP 代理。注意返回值 wrappedBean 一路被覆盖——before/after 阶段都可以「换对象」,这正是代理能替换原始 Bean 的机制。
AOP 代理为什么放在 postProcessAfterInitialization(after 阶段):先想:代理为什么要等依赖注入完、init 执行完才生成?——因为要包裹的是「成品」:
- 目标 Bean 已完整就绪:依赖已注入、init 已执行,包裹的是「成品」。
- before/after 分阶段不干扰:before 阶段在原始对象上处理
@PostConstruct,after 阶段在代理对象上生成代理。 - 循环依赖例外:需提前暴露引用时,
getEarlyBeanReference()在三级缓存阶段提前生成代理,after 阶段检测到earlyProxyReferences已有记录则跳过,不重复代理。
第 2 步的依赖注入和第 7 步的代理,正好是循环依赖三级缓存要解决的两个点,下面展开。
④ 循环依赖(三级缓存)
一句话结论(30s)
Spring 用三级缓存解决单例 Bean 的字段/setter 循环依赖。因为实例化(new)和初始化(注入)是分离的,可以先 new 出半成品提前暴露引用、打破互相等待的循环;三级缓存的精髓是用 ObjectFactory 把「要不要生成代理」的决策推迟到第一次被引用时,保证代理唯一性。
核心原理(2min)
先想:A 依赖 B、B 依赖 A,两个都等对方先建好,这不就死锁了吗?——破局点在于「实例化(new)和注入是分开的两步」,可以先把半成品 new 出来提前暴露。
A 和 B 互相 @Autowired。A 创建时先 new 出 A 的半成品,暴露出去;B 创建时拿到 A 的半成品,B 完成;A 再注入已完成的 B,A 也完成。
| 缓存 | 存什么 | 作用 |
|---|---|---|
一级 singletonObjects | 完整就绪的 Bean | 最终结果 |
二级 earlySingletonObjects | 早期引用(原始或代理) | 打破循环 |
三级 singletonFactories | ObjectFactory(延迟判断) | 保证代理唯一性 |
为什么需要三级:先想:二级缓存存半成品不就够了吗,为什么还要多一层三级?——关键在「代理」:如果 A 需要 AOP(如 @Transactional),B 拿到的必须是 A 的代理而不是原始对象。但代理在初始化完成后才生成,B 可能在 A 初始化完成前就拿走了二级缓存里的原始 A——最终一级缓存是代理、B 持有的是原始对象,同一个单例出现两个不同引用,单例语义被破坏。
底层深入(5-10min)
getSingleton——三级缓存查找的真实代码(DefaultSingletonBeanRegistry):
protected @Nullable Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock.
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
if (!this.singletonLock.tryLock()) {
// Avoid early singleton inference outside of original creation thread.
return null;
}
try {
// Consistent creation of early reference within full singleton lock.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// Singleton could have been added or removed in the meantime.
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
else {
singletonObject = this.singletonObjects.get(beanName);
}
}
}
}
}
finally {
this.singletonLock.unlock();
}
}
}
return singletonObject;
}
三级查找顺序在源码里一清二楚:先查一级 singletonObjects,只有「Bean 正在创建中」才继续查二级 earlySingletonObjects,最后才触发三级 singletonFactories 里的 ObjectFactory.getObject()(即 getEarlyBeanReference),并把结果从三级移到二级。singletonFactories.remove(beanName) != null 这行保证了「延迟决策只执行一次」——代理只生成一次。
下面再解释 ObjectFactory 里装了什么:
// A 实例化后不放原始对象, 而是放一个 ObjectFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// getEarlyBeanReference 内部:
if (需要代理) {
earlyProxyReferences.put(beanName, bean); // 记录已提前代理
return wrapIfNecessary(bean, ...); // 提前生成代理
}
return bean; // 不需要代理, 返回原始对象
当 B 调 getBean(A) 触发三级缓存的 ObjectFactory.getObject(),此时按需决定:要 AOP → 生成早期代理;不要 → 返回原始对象,结果存二级缓存。after 阶段检测到 earlyProxyReferences 有记录则跳过重复代理。
构造器注入不能解决循环依赖:先想:同样是循环依赖,为什么字段注入能解、构造器注入解不了?——差别在「半成品」能不能提前暴露出来:
A 实例化: new A(b) → 需要 B
B 实例化: new B(a) → 需要 A
→ 谁都无法 new 出来 → BeanCurrentlyInCreationException
构造器调用发生在实例化阶段,此时还没有任何缓存暴露半成品,双方都拿不到对方引用。解法:@Lazy 注入代理占位符,首次调用时才真正获取。
注意:Spring 2.6+ 默认禁止循环依赖,需显式开启。官方推荐重构设计而非依赖三级缓存——循环依赖通常是设计问题。
⑤ 事务失效
一句话结论(30s)
@Transactional 失效的根因几乎都是**「调用没经过 AOP 代理」**。因为事务本身就是靠「AOP 代理 + TransactionInterceptor + PlatformTransactionManager」实现的,只要绕过了代理,注解就形同虚设。
核心原理(2min)
先想:一个 @Transactional 注解,运行时到底靠什么把方法包进事务?——它自己什么也不做,全靠 AOP 代理拦一层,所以一旦调用没走代理,注解就失效——这是后面所有失效场景的总根因。
事务本质:TransactionInterceptor 在目标方法前后通过事务管理器管理事务:
public Object invoke(MethodInvocation invocation) {
TransactionStatus status = txManager.getTransaction(attr);
try {
Object result = invocation.proceed(); // 目标方法
txManager.commit(status);
return result;
} catch (Throwable ex) {
txManager.rollback(status);
throw ex;
}
}
7 种传播行为:
| 传播行为 | 含义 |
|---|---|
| REQUIRED(默认) | 有事务则加入,无则新建 |
| REQUIRES_NEW | 挂起当前事务,新建独立事务 |
| NESTED | 当前事务中创建 Savepoint 嵌套子事务 |
| SUPPORTS / NOT_SUPPORTED / NEVER / MANDATORY | 按名字语义 |
失效 6 场景(重点记前三,先想:这 6 个场景能不能归成同一句话?——「这段调用走代理了吗」):
- 同类
this.method()调用——绕过代理,拦截器没执行。 - 非 public 方法——CGLIB 靠继承重写,
private/protected无法拦截。 - 异常被 catch 吞掉——事务感知不到异常,不回滚。
- 未配
rollbackFor——默认只回滚RuntimeException和Error,受检异常不回滚。 - 多数据源——只管理当前事务管理器绑定的数据源。
- final 方法——CGLIB 无法重写。
底层深入(5-10min)
TransactionInterceptor.invoke——事务怎么被包进方法(org.springframework.transaction.interceptor.TransactionInterceptor):
@Override
public @Nullable Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public @Nullable Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public void onRollback(Throwable failure, TransactionExecution execution) {
MethodRollbackEvent event = new MethodRollbackEvent(invocation, failure, execution);
logger.trace(event, failure);
// ...(省略发布 MethodRollbackEvent)
}
});
}
TransactionInterceptor 只是一个 MethodInterceptor:它把 invocation.proceed()(真正调目标方法)包进 invokeWithinTransaction,后者才负责 getTransaction/commit/rollback。注意它必须挂在代理的拦截器链上才会被执行——同类 this.method() 直接调目标对象,根本不会走到这个 invoke,@Transactional 自然失效。这就是为什么「调用是否经过代理」能统一解释所有失效场景。
REQUIRES_NEW vs NESTED 的本质区别:先想:都是「子事务可以独立回滚」,二者差别到底在哪?——一个换了连接,一个不换只打保存点:
- REQUIRES_NEW:
transactionManager.suspend()挂起当前事务(把当前 Connection 从 ThreadLocal 移除),用新 Connection 开全新独立事务,子事务提交/回滚与主事务完全无关。 - NESTED:不挂起、不新建 Connection,只在当前事务里
conn.setSavepoint(),子事务回滚只回到保存点,主事务继续;要求数据库支持 Savepoint(MySQL InnoDB 支持)。
同类 this 调用的解法:
public void create() {
this.pay(); // 绕过代理 → 事务失效
}
// 解法1: 抽到另一个 Bean 注入后调用
// 解法2: AopContext.currentProxy() 拿代理再调
记忆锚点:所有失效场景都能用一句话定位——「这段调用,走代理了吗?」
⑥ 自动装配(@Autowired / @Resource)
一句话结论(30s)
@Autowired 是 Spring 自己的注解,默认按类型 byType 注入、找不到再按名字;@Resource 是 JSR-250 标准,默认按名字 byName 注入、找不到再按类型。因为两者来源和注入优先级不同,混用时要注意 @Qualifier 和 @Primary 的配合。
核心原理(2min)
先想:一个接口有多个实现类时,Spring 怎么知道该注入哪个?——它有一套「裁决顺序」,记住这个顺序就懂了装配。
- @Autowired:byType 优先;同类型多个 Bean 时,按
@Primary→@Qualifier→ 字段名匹配依次裁决;required=false允许缺省。 - @Resource:byName 优先(
name属性或字段名),找不到再退化为 byType;没有required属性。 - 装配入口:
AutowiredAnnotationBeanPostProcessor在populateBean阶段(生命周期的第 2 步)通过postProcessProperties完成注入。 - 构造器注入:Spring 4.3+ 单构造器自动注入,无需
@Autowired。
底层深入(5-10min)
按类型注入的裁决流程(DefaultListableBeanFactory.resolveDependency):先想:byType 找到多个候选时,Spring 按什么顺序裁决?——
- byType 找到唯一候选 → 直接注入。
- 找到多个候选 → 依次按
@Primary优先 →@Qualifier指定 → 名称与字段名匹配 → 都不满足抛NoUniqueBeanDefinitionException。 - 找不到且
required=true→NoSuchBeanDefinitionException;required=false→ 跳过(留 null)。
裁决流程的真实源码(DefaultListableBeanFactory.doResolveDependency):
public @Nullable Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
// Step 1: pre-resolved shortcut for single bean match, for example, from @Autowired
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class<?> type = descriptor.getDependencyType();
// Step 2: pre-defined value or expression, for example, from @Value
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
// ...(省略 @Value 占位符解析)
}
// ...(省略 Step 3 名称/Qualifier 快捷匹配、Step 4 集合注入)
// Step 5: determine single candidate
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesArrayCollectionOrMap(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
// Step 6: validate single result
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
return resolveInstance(instanceCandidate, descriptor, type, autowiredBeanName);
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
这段就是上面裁决流程的落地代码:matchingBeans.size() > 1 时走 determineAutowireCandidate(内部按 @Primary → @Qualifier → 字段名裁决),仍判不出唯一就抛 resolveNotUnique(对应 NoUniqueBeanDefinitionException);只有一个候选直接取。findAutowireCandidates 里会识别 FactoryBean 并取 getObject() 产品,所以注入时声明产品类型就能拿到 FactoryBean 生产的东西。
与循环依赖的关系:三级缓存能解决的循环依赖只对字段/setter 注入生效——因为 populateBean 发生在实例化之后,半成品已经能暴露;构造器注入在实例化阶段,无法用三级缓存解决。
FactoryBean 的 & 前缀:getBean("&xxx") 拿工厂本身,getBean("xxx") 拿产品——注入时若声明的是产品类型,Spring 会识别 FactoryBean 并取 getObject() 的结果。
追问清单(10 题 · 一句话结论 + 展开)
1. IOC 容器启动过程?BeanFactory 和 ApplicationContext 的区别?
结论:启动 = 「解析配置 → 生成 BeanDefinition → BFPP 改元数据 → 实例化单例」;BeanFactory 是最基础容器、getBean() 时才创建(懒),ApplicationContext 是增强版、启动时预实例化并额外提供事件/国际化/AOP。
展开:先解析 XML/注解/JavaConfig 生成 BeanDefinition 注册到 BeanDefinitionRegistry;然后 BeanFactoryPostProcessor 修改元数据(如解析 ${} 占位符);最后实例化所有非懒加载单例(反射 new → 注入 → init → 代理)。BeanFactory 只是「拿来才做」,ApplicationContext 是「启动就做全」,且是后者才支持 @EventListener 事件、getMessage 国际化等企业特性。
2. AOP 底层实现?JDK 动态代理和 CGLIB 的区别?Spring 默认用哪个?
结论:AOP 靠动态代理织入,JDK 基于接口 + 反射(慢 2-3 倍),CGLIB 基于子类 + FastClass 索引(非反射、快);Spring 有接口默认 JDK,Spring Boot 2.x 起强制 CGLIB。
展开:JDK 生成的 $Proxy0 继承 Proxy 实现接口,调用走 InvocationHandler.invoke → Method.invoke 两层反射;CGLIB 生成目标类的子类,用 MethodProxy + FastClass 按 index 直接调用父类方法。Boot 强制 CGLIB 的原因:字段注入需要代理是目标实现类实例,JDK 代理只实现接口、不是实现类。JDK 只代理接口方法,CGLIB 不能代理 final 类/方法。
3. Bean 的生命周期完整说一下?
结论:实例化 → 属性填充 → Aware 回调 → postProcessBeforeInitialization(@PostConstruct)→ afterPropertiesSet → init-method → postProcessAfterInitialization(AOP 代理)→ 使用 → 销毁。
展开:先反射 new 原始对象,populateBean 注入 @Autowired/@Value;然后走 Aware 回调(BeanNameAware/BeanFactoryAware/ApplicationContextAware);初始化阶段 @PostConstruct 在 before 阶段、afterPropertiesSet 和 init-method 依次执行;AOP 代理统一在 after 阶段生成(包裹成品)。销毁按 @PreDestroy → DisposableBean.destroy → destroy-method。
4. Spring 怎么解决循环依赖?三级缓存分别存什么?
结论:一级 singletonObjects 存成品,二级 earlySingletonObjects 存半成品(打破循环),三级 singletonFactories 存 ObjectFactory(延迟判断是否代理,保证代理唯一性)。
展开:A 实例化后把原始对象包成 ObjectFactory 放三级;B 创建时 getBean(A) 触发 getEarlyBeanReference,需要 AOP 就提前生成代理、否则返回原始对象,结果存二级;A 的 after 阶段检测到已提前代理则跳过,保证一级和 B 持有的 A 是同一个对象。三级缓存的核心价值是「延迟决策 + 标记去重」,避免单例出现两个不同引用。
5. 构造器注入能解决循环依赖吗?为什么?
结论:不能。因为构造器在实例化阶段就被调用,此时还没有任何缓存暴露半成品,双方都无法 new 出对方,抛 BeanCurrentlyInCreationException。
展开:new A(b) 需要 B 作为构造参数,new B(a) 需要 A——实例化这一步就死锁,三级缓存要在「实例化完成、属性填充阶段」才有用武之地。解法是 @Lazy:给构造参数注入一个代理占位符,首次真正调用时才去容器取,打破实例化阶段的循环。
6. @Transactional 什么情况下会失效?举 3 个场景
结论:根因是调用没经过 AOP 代理,最典型三类——同类 this 调用、非 public 方法、异常被 catch 吞掉。
展开:① this.pay() 调的是原始对象方法,绕过代理,拦截器不执行(抽 Bean 或 AopContext.currentProxy() 解);② private/protected 方法 CGLIB 无法重写;③ try-catch 吞掉异常,事务感知不到、不回滚。补充:未配 rollbackFor(默认只回滚 RuntimeException/Error,受检异常不回滚)、多数据源(只管当前事务管理器的数据源)、final 方法。
7. 事务传播行为有哪些?项目里最常用哪种?
结论:7 种里最常用 REQUIRED(默认,有事务就加入、没有就新建);需要「无论如何都要独立提交」时用 REQUIRES_NEW。
展开:7 种 = REQUIRED / REQUIRES_NEW / NESTED / SUPPORTS / NOT_SUPPORTED / NEVER / MANDATORY。REQUIRES_NEW 是 suspend() 挂起当前事务 + 新 Connection 开独立事务,提交回滚与主事务无关;NESTED 不挂起、不新开连接,用 setSavepoint() 做嵌套子事务,子事务回滚只回保存点、主事务继续。日常 CRUD 用默认 REQUIRED 即可,记账/审计日志这类「失败也不能跟着主事务回滚」的场景才用 REQUIRES_NEW。
8. @Autowired 和 @Resource 的区别?Spring 怎么按类型自动注入?
结论:@Autowired 是 Spring 的、默认 byType 再按名字;@Resource 是 JSR-250 的、默认 byName 再按类型。
展开:@Autowired 同类型多个候选时按 @Primary → @Qualifier → 字段名依次裁决,required=false 允许缺省;@Resource 先按 name/字段名找,找不到退化为 byType。按类型注入的入口是 AutowiredAnnotationBeanPostProcessor 在 populateBean 阶段调 resolveDependency,唯一候选直接注入、多候选按优先级裁决、无候选且 required=true 抛 NoSuchBeanDefinitionException。
9. Spring Boot 自动配置原理?@SpringBootApplication 做了什么?
结论:@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan;@EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector) 加载约 150 个候选配置类,用 @Conditional 过滤后生效,实现「引入依赖即可用」。
展开:selectImports() 读取 META-INF/spring/...AutoConfiguration.imports(3.x)或 spring.factories(2.x)拿到候选列表;对每个配置类做 @ConditionalOnClass(jar 在 classpath)、@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnProperty 判断,满足才注册其 @Bean。--debug 启动可见 Condition Evaluation Report。坑:@ConditionalOnMissingBean 按处理顺序判断,可能先注册自动配置再注册用户 Bean 导致类型冲突,用 @AutoConfigureBefore/After 或 @Primary 防。
10. Spring 用了哪些设计模式?每个在哪里?
结论:工厂(BeanFactory/FactoryBean)、模板方法(JdbcTemplate/refresh())、代理(AOP)、观察者(事件机制)、单例(默认 scope)、适配器(HandlerAdapter)。
展开:FactoryBean 把复杂对象创建封装进 getObject(),getBean("&name") 拿工厂本身;JdbcTemplate.execute 固定「获取连接 → 异常翻译 → 关资源」骨架、回调只写 SQL,AbstractApplicationContext.refresh() 是固定 14 步的模板方法;AOP 是代理模式(调用方不知情透明替换);ApplicationEvent + @EventListener + ApplicationEventPublisher 是观察者模式(@TransactionalEventListener 绑定事务边界、@Async 异步);容器单例 Bean 默认就是单例模式;DispatcherServlet 的 HandlerAdapter 是适配器,屏蔽不同 Handler 的差异。