Skip to content
Go back

Spring 面试回答——IOC、AOP、Bean 生命周期与循环依赖

Spring 面试回答

本文把 Spring 面试高频考点拆成六个子主题——IOC、AOP、Bean 生命周期、循环依赖、事务失效、自动装配,每个主题都按「三版本」展开:一句话结论(30s)、核心原理(2min)、底层深入(5-10min),末尾附 10 个追问点清单。


① IOC(控制反转)

一句话结论(30s)

IoC 就是把「对象的创建和依赖管理」从业务代码反转到容器——由 Spring 统一 new 对象、管生命周期、注入依赖。因为对象之间的依赖关系一旦变复杂,自己 new 会让改动一个依赖就牵连所有调用方;交给容器后只用注解声明依赖,改一处即可,还能天然拿到单例、懒加载和 AOP。

核心原理(2min)

先想:IoC 到底「反转」了什么?——反转的是「谁创建、谁管理依赖」的主动权:原来是「谁用谁 new」,现在是「容器建好给你」,下面的每个概念都是这条主线的延伸。

底层深入(5-10min)

BeanDefinition 是「食谱」:先想:你只写了一个 @Component,Spring 凭什么就帮你把对象建好了?——它不直接 new,而是先登记「食谱」:beanClassName / scope / lazyInit / initMethodName / propertyValues。扫描到 @Component 只生成定义存注册表,还没实例化——注册表是「待办清单」。

BFPP vs BPP——阶段分离的核心

BeanFactoryPostProcessorBeanPostProcessor
作用对象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 步顺序(obtainFreshBeanFactoryinvokeBeanFactoryPostProcessorsregisterBeanPostProcessorsfinishBeanFactoryInitialization),子类 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)

先想:日志、事务、权限这类逻辑横在几十个方法里、到处复制粘贴,怎么抽出来还不侵入业务?——答案就是「动态代理织入」,下面每个术语都是它的零件。

JDK 动态代理CGLIB
生成方式Proxy.newProxyInstance()ASM 生成子类字节码
前提需要接口不需要接口
限制只能代理接口方法final 类/方法不可代理
方法调用反射 Method.invoke()FastClass 索引(非反射)
性能慢 2-3 倍

底层深入(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 一个 ReflectiveMethodInvocationproceed() 依次穿过 @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 里跑 @PostConstructCommonAnnotationBeanPostProcessor),invokeInitMethods 里先 afterPropertiesSet() 再自定义 init-method,最后 applyBeanPostProcessorsAfterInitialization 生成 AOP 代理。注意返回值 wrappedBean 一路被覆盖——before/after 阶段都可以「换对象」,这正是代理能替换原始 Bean 的机制。

AOP 代理为什么放在 postProcessAfterInitialization(after 阶段):先想:代理为什么要等依赖注入完、init 执行完才生成?——因为要包裹的是「成品」:

  1. 目标 Bean 已完整就绪:依赖已注入、init 已执行,包裹的是「成品」。
  2. before/after 分阶段不干扰:before 阶段在原始对象上处理 @PostConstruct,after 阶段在代理对象上生成代理。
  3. 循环依赖例外:需提前暴露引用时,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早期引用(原始或代理)打破循环
三级 singletonFactoriesObjectFactory(延迟判断)保证代理唯一性

为什么需要三级:先想:二级缓存存半成品不就够了吗,为什么还要多一层三级?——关键在「代理」:如果 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 个场景能不能归成同一句话?——「这段调用走代理了吗」):

  1. 同类 this.method() 调用——绕过代理,拦截器没执行。
  2. 非 public 方法——CGLIB 靠继承重写,private/protected 无法拦截。
  3. 异常被 catch 吞掉——事务感知不到异常,不回滚。
  4. 未配 rollbackFor——默认只回滚 RuntimeExceptionError,受检异常不回滚。
  5. 多数据源——只管理当前事务管理器绑定的数据源。
  6. 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 的本质区别:先想:都是「子事务可以独立回滚」,二者差别到底在哪?——一个换了连接,一个不换只打保存点:

同类 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 怎么知道该注入哪个?——它有一套「裁决顺序」,记住这个顺序就懂了装配。

底层深入(5-10min)

按类型注入的裁决流程DefaultListableBeanFactory.resolveDependency):先想:byType 找到多个候选时,Spring 按什么顺序裁决?——

  1. byType 找到唯一候选 → 直接注入。
  2. 找到多个候选 → 依次按 @Primary 优先 → @Qualifier 指定 → 名称与字段名匹配 → 都不满足抛 NoUniqueBeanDefinitionException
  3. 找不到且 required=trueNoSuchBeanDefinitionExceptionrequired=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 存半成品(打破循环),三级 singletonFactoriesObjectFactory(延迟判断是否代理,保证代理唯一性)。

展开: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。按类型注入的入口是 AutowiredAnnotationBeanPostProcessorpopulateBean 阶段调 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 默认就是单例模式;DispatcherServletHandlerAdapter 是适配器,屏蔽不同 Handler 的差异。


Share this post on:

Previous Post
SQL陷阱——NOT EXISTS vs NOT IN,NULL毁掉一切
Next Post
RocketMQ 面试回答——架构、可靠消息、高可用与死信队列