Spring IoC 与 AOP

共 32 题
📑 题目列表 32 题
#
★★★

1. @Autowired 的依赖解析流程如何按类型匹配,多个候选 Bean 时怎样经 @Qualifier 与按名称回退确定最终注入对象

请说明 @Autowired 注解依赖注入的解析流程,它是如何先按类型匹配候选 Bean,当出现多个候选时又如何通过 @Qualifier 和按字段名回退来确定最终的注入对象?

  • @Autowired 的 defaultRequired 与 byType 解析机制
  • 多个候选时的 @Primary、@Qualifier、字段名回退三级优先级
  • defaultAutowireCandidates 与候选查找的底层逻辑

@Autowired 采用的是 byType(按类型)解析。流程大致为:首先根据注入点的类型(字段类型或构造方法参数类型)调用 getBeanNamesForType 查找所有匹配的候选 Bean。若恰好只有一个候选,直接选它;若存在多个候选,Spring 会依次应用消歧规则:先通过 @Qualifier 指定的限定名(未标注时也可按 Bean 名称)过滤候选,在剩余候选中优先选择标记了 @Primary 的 Bean,最后才回退到"按字段名/参数名与 Bean 名称匹配"的规则。若这些规则仍无法唯一确定,且注入点是集合(List/Map)则全部注入;否则抛出 NoUniqueBeanDefinitionException。若一个候选都没有,且 required=true(默认),则抛出 NoSuchBeanDefinitionException。

这体现了 Spring 依赖注入的"单一候选优先、多候选逐级消歧"的设计哲学。@Primary 用于声明默认优先的 Bean,@Qualifier 提供显式限定,而按名称回退是最后兜底。理解这一顺序有助于排查 NoUniqueBeanDefinitionException 这类最常见的注入错误。

@Service
public class OrderService {
    // 多候选时按名称回退:字段名 userServiceImpl 匹配 userServiceImpl 这个 Bean
    @Autowired
    private UserService userServiceImpl;

    // 显式限定
    @Autowired
    @Qualifier("cacheUserService")
    private UserService cacheUserService;
}
#
★★★

2. @ConditionalOnClass/@ConditionalOnMissingBean/@ConditionalOnProperty 的评估顺序与失效边界

请说明 Spring Boot 自动配置中 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等条件注解的评估顺序,以及它们各自的失效边界?

  • 条件注解的评估顺序(OnClassCondition → OnBeanCondition → OnPropertyCondition)
  • @ConditionalOnMissingBean 的失效边界(类型检查而非名称)
  • 条件注解与 BeanDefinition 处理顺序的关系

Spring Boot 通过 AutoConfigurationImportSelector 收集自动配置类,并按照固定顺序评估条件注解:先是 @ConditionalOnClass(判断类路径是否存在指定类)、再是 @ConditionalOnBean/@ConditionalOnMissingBean(判断容器中是否已有匹配的 Bean)、最后是 @ConditionalOnProperty(判断配置属性)。评估顺序是 @ConditionalOnClass 优先于 @ConditionalOnBean 优先于 @ConditionalOnProperty。失效边界方面:@ConditionalOnMissingBean 只基于当前已注册的 BeanDefinition 做类型判断,无法处理泛型 Bean 及依赖处理顺序的 Bean,条件注解不能用于 @Bean 方法内部保证 Bean 的创建顺序;@ConditionalOnProperty 只校验配置项,本身不感知类路径变化。

顺序保证"类路径 → 容器 Bean → 配置属性"的评估逻辑,避免出现类缺失时仍去查询 Bean 导致异常。@ConditionalOnMissingBean 的失效边界是设计上使其无法感知未来才注册的 Bean,因此它常用于"给用户扩展留口"——用户自定义 Bean 后自动配置的同类型 Bean 自动失效。

#
★★★

3. @Import 三种用法(普通类、ImportSelector、ImportBeanDefinitionRegistrar)

请说明 @Import 注解的三种典型用法——导入普通类、导入 ImportSelector、导入 ImportBeanDefinitionRegistrar——各自的适用场景与实现机制?

  • 普通类导入:直接注册 BeanDefinition
  • ImportSelector:按条件动态返回要导入的类名
  • ImportBeanDefinitionRegistrar:编程式注册 BeanDefinition

@Import 有三种用法:一是直接导入普通类(@Configuration 或普通 @Component),Spring 会将其注册为 BeanDefinition;二是导入 ImportSelector 实现类,其 selectImports 方法接收 AnnotationMetadata 并按条件返回需要导入的类名数组,若同时实现 DeferredImportSelector 则延迟到所有配置类处理完之后再执行(这是自动配置去重的基础);三是导入 ImportBeanDefinitionRegistrar 实现类,其 registerBeanDefinitions 方法可编程式地向容器注册 BeanDefinition,灵活性最高,常用于动态注册多个 Bean。

三者从"静态到动态"层级递进:普通类最直接、ImportSelector 提供基于注解元数据的动态选择、ImportBeanDefinitionRegistrar 提供最底层的编程控制。@EnableXxx 系列注解(如 @EnableAsync、@EnableTransactionManagement)正是通过组合 @Import 这三种方式实现的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)   // 方式二:ImportSelector
public @interface EnableMyFeature {}

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.MyConfig"};
    }
}
#
★★★

4. @Primary 与 Bean 优先级的关系

请说明 @Primary 注解在 Spring 依赖注入中的作用,它与 Bean 优先级和 @Qualifier 的关系是什么?

  • @Primary 标记默认首选的 Bean
  • @Primary 与 @Qualifier 的优先级:@Qualifier 显式指定优先于 @Primary
  • 对集合注入的影响

@Primary 用于标记在类型匹配出现多个候选 Bean 时默认优先选择的那个 Bean。它本质上是给 Bean 打上一个"默认优先"的标记,解析顺序为:@Qualifier 显式限定 > @Primary 标记 > 字段名回退。在向 List/Map 注入全部候选时 @Primary 不影响集合内容,只是主候选;当注入点同时标注 @Qualifier 时,@Qualifier 相当于显式锁定,优先级高于 @Primary。@Primary 可以放在 @Configuration 的 @Bean 方法上,也可以放在 @Component 类上。

它解决的是"同类型多实现时默认选哪个"的问题,常用于接口有多个实现但希望默认使用主实现、而其他实现通过 @Qualifier 显式选择的场景,是一种低侵入的默认策略。

@Configuration
public class Config {
    @Bean
    @Primary
    public PaymentService alipay() { return new AlipayService(); }

    @Bean
    public PaymentService wechat() { return new WechatService(); }
}
#
★★★

5. @Profile 与多环境 Bean 注册

请说明 @Profile 注解如何实现多环境下的 Bean 注册差异,以及它与 Spring Boot 配置文件的激活机制如何配合?

  • @Profile 的条件化 Bean 注册原理
  • @ActiveProfiles 与 spring.profiles.active 配置
  • 多环境(dev/test/prod)的常见实践

@Profile 标注在 @Configuration 类或 @Bean 方法上,指定的 profile 处于激活状态时该 Bean 才会注册,本质上是基于 Condition 的条件装配。激活 profile 的方式有:命令行参数 --spring.profiles.active、环境变量 SPRING_PROFILES_ACTIVE、application.yml 中的 spring.profiles.active,以及测试中的 @ActiveProfiles。未匹配任何 profile 的 Bean 在默认 profile 下仍会注册。多环境实践通常用 application-dev.yml、application-prod.yml 等按 profile 分文件,并配合 @Profile 选择不同实现(如 mock 与真实实现)。

@Profile 让"同一套代码、不同运行环境注册不同 Bean"成为可能,减少了环境切换时的代码改动,是环境隔离与配置外置的关键手段。

@Configuration
@Profile("prod")
public class ProdConfig {
    @Bean
    public DataSource dataSource() { return new HikariDataSource(); }
}
#
★★★

6. AOP 与 @Transactional 的协作边界

请说明 Spring AOP 与 @Transactional 注解的协作关系,事务功能是如何借助 AOP 代理实现的,以及它们的协作边界在哪里?

  • @Transactional 基于 AOP 代理实现
  • TransactionInterceptor 作为 AOP 通知
  • 代理生效的前提(public 方法、外部调用)

@Transactional 是 Spring 声明式事务的入口,其底层正是通过 AOP 代理实现的。Spring 在启动时基于 @EnableTransactionManagement 引入 TransactionInterceptor 作为环绕通知,为标注了 @Transactional 的 Bean 生成代理。调用方法时,TransactionInterceptor 在方法进入前开启事务、方法成功返回后提交、抛出异常时按回滚规则回滚。协作边界在于:只有通过代理对象调用的方法事务才生效,因此自调用(this.xxx())和 final/private 方法无法触发事务,因为代理拦截不到。

理解 AOP 与 @Transactional 的协作,是把控事务失效的钥匙——事务生效依赖代理,失去代理(自调用、final、非 Spring 管理)事务即失效。这也是为什么把事务方法放在独立 Bean 中通过外部调用是常见最佳实践。

#
★★★

7. AOP 与 final 方法的限制(无法代理)

请说明 Spring AOP 为什么无法对 final 方法进行代理,以及 final 类/方法对 CGLIB 和 JDK 动态代理分别有什么影响?

  • JDK 动态代理基于接口,与 final 无关
  • CGLIB 通过子类继承实现,final 方法无法被覆写
  • final 类无法被 CGLIB 代理

Spring AOP 有两种代理方式:JDK 动态代理基于接口,通过 Proxy 生成接口实现类,本身不涉及 final;CGLIB 通过生成目标类的子类并覆写方法实现代理,因此无法代理 final 类(不能继承)和 final 方法(不能覆写)。Spring 4+ 默认使用 CGLIB 代理,所以当类方法被 final 修饰时,CGLIB 无法生成拦截,实际上该方法不会走代理。此外 static 方法也不可被代理。

这是"面向对象可覆写性"与"代理机制"的天然约束。设计时避免将受 AOP 管理(事务、安全、缓存)的方法标为 final,才能保证代理拦截生效。

#
★★★

8. AOP 在事务管理、日志、安全等横切关注点的应用

请说明 AOP(面向切面编程)在事务管理、日志记录、安全控制等横切关注点中的应用,以及使用 AOP 的统一优势?

  • 横切关注点(cross-cutting concerns)的概念
  • AOP 在事务、日志、安全、性能监控中的典型应用
  • 避免重复代码、关注点分离

AOP 用于把横切关注点从业务代码中抽取出来,形成独立的切面。典型应用包括:事务管理(@Transactional 通过 TransactionInterceptor 统一开启/提交/回滚,业务代码无需手写事务逻辑)、日志记录(通过 @Around 通知统一记录方法入参、耗时、异常)、安全控制(@PreAuthorize 方法级鉴权通过 MethodSecurityInterceptor 实现)、性能监控(埋点统计方法执行时间)、缓存(@Cacheable 通过 CacheInterceptor 实现)、审计与异常兜底。统一优势是:业务代码只关心核心逻辑,横切逻辑集中维护,改动一处即可全量生效,实现关注点分离。

AOP 的本质是"把与业务无关的重复逻辑抽到切面,通过代理织入",极大提升了代码复用性与可维护性,是 Spring 生态中事务、安全、缓存等能力共同的底层基石。

#
★★★

9. AOP 核心概念(切面、连接点、通知、切入点、引入、目标对象)

请说明 AOP 的核心概念:切面(Aspect)、连接点(Join Point)、通知(Advice)、切入点(Pointcut)、引入(Introduction)、目标对象(Target)分别是什么?

  • 六个核心概念的定义
  • 切面与切入点、通知的关系
  • 通知的五种类型(Before/After/AfterReturning/AfterThrowing/Around)

AOP 的核心概念包括:切面(Aspect)是横切关注点与横切行为的模块化封装,如一个日志切面;连接点(Join Point)是程序执行过程中可被拦截的点,如方法调用、异常抛出;通知(Advice)是连接点处执行的具体动作,有 Before、After、AfterReturning、AfterThrowing、Around 五种类型;切入点(Pointcut)是匹配连接点的表达式,用于决定通知织入哪些连接点;引入(Introduction)可在不修改目标类的情况下为其添加新接口或方法;目标对象(Target)是被切面作用的对象,即真正被代理的业务 Bean。

把握"切面包含切入点和通知,切入点决定在哪织入,通知决定织入什么动作"这一关系,就抓住了 AOP 的骨架。切入点用表达式(如 execution(* com.x..service..*(..)))匹配方法。

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void before() { System.out.println("before method"); }
}
#
★★★

10. ApplicationContext 与 BeanFactory 的差异

请说明 ApplicationContext 与 BeanFactory 的差异,为什么应用普遍使用 ApplicationContext 而非直接使用 BeanFactory?

  • BeanFactory 是 IoC 容器的基础接口
  • ApplicationContext 在 BeanFactory 之上的扩展能力
  • 预加载、国际化、事件、AOP 自动装配等

BeanFactory 是 Spring IoC 容器的最底层接口,提供最基本的 Bean 管理能力(getBean、按类型获取、作用域管理),采用懒加载(首次获取时才实例化)。ApplicationContext 继承并扩展了 BeanFactory,是"增强版容器",额外提供:资源加载(ResourceLoader)、国际化(MessageSource)、事件发布(ApplicationEventPublisher)、Bean 生命周期管理、AOP 自动代理、环境抽象(Environment),且默认对 Singleton Bean 预加载(饿汉式),启动时即完成实例化。因此应用层普遍使用 ApplicationContext,其子类 WebApplicationContext 又扩展了 Web 环境能力。

差异本质是"基础容器 vs 完整框架容器"。ApplicationContext 的一切扩展都建立在 BeanFactory 之上,掌握二者的关系有助于理解容器启动顺序与能力边界。

#
★★★

11. ApplicationContext.getBeansOfType 与运行时类型查找

请说明 ApplicationContext.getBeansOfType 方法的作用,以及它相对于 getBean 的运行时类型查找能力?

  • getBeansOfType 按类型返回所有匹配 Bean 的 Map
  • 按类型、按名称、按注解的查找体系
  • 与依赖注入类型解析的关联

getBeansOfType(Class type) 返回容器中所有匹配指定类型的 Bean 映射(key 为 Bean 名称,value 为实例),常用于运行时动态获取某一接口的所有实现并统一处理。Spring 的查找体系包括:getBean(name) 按名称、getBean(Class) 按类型、getBeansOfType 按类型批量、getBeansWithAnnotation 按注解批量。getBeansOfType 只在运行时确定类型,若容器尚未初始化对应 Bean 会触发实例化,因此适合在容器启动完成后用于动态装配场景。

getBeansOfType 是"运行时策略分发"的利器,例如通过接口注入所有 Handler 实现再按类型分发,是插件化设计的基础。它返回 Map 而非单一 Bean,天然支持多实现场景。

Map<String, Handler> handlers = applicationContext.getBeansOfType(Handler.class);
handlers.forEach((name, handler) -> handler.process());
#
★★★

12. ApplicationContextAware/BeanNameAware 的应用场景

请说明 ApplicationContextAware 与 BeanNameAware 接口的应用场景,以及它们在 Bean 生命周期中的调用时机?

  • ApplicationContextAware 让 Bean 获得容器引用
  • BeanNameAware 让 Bean 知道自己的 Bean 名称
  • 通过 Aware 回调在初始化阶段获取容器资源

BeanNameAware 接口的 setBeanName 方法在 Bean 创建时被调用,让 Bean 获得自己在容器中的注册名称;ApplicationContextAware 接口的 setApplicationContext 方法在 Bean 创建及属性填充后调用,让 Bean 获得容器的引用,从而在运行时执行 getBean、发布事件、获取资源等操作。二者都属于 Aware 回调家族,在 Bean 实例化阶段由容器注入,早于初始化回调(InitializingBean 的 afterPropertiesSet)。应用场景如:在 Bean 中通过 getApplicationContext().getBeansOfType 动态获取其他 Bean,或需在日志中输出自身 Bean 名称。

Aware 接口让 Bean 反向感知容器上下文,是"容器回调"模式的体现。但过度依赖容器引用会增加耦合,官方也更推荐通过构造器注入或 @Autowired 获取所需依赖,仅在确实需要容器能力时才使用 Aware。

#
★★★

13. AspectJ 的静态织入与 Spring AOP 的运行时织入对比

请对比 AspectJ 的静态织入(编译期/加载期织入)与 Spring AOP 的运行时织入(动态代理)在原理、性能、能力上的差异?

  • AspectJ 编译期/加载时织入(字节码增强)
  • Spring AOP 运行时动态代理(JDK 代理/CGLIB)
  • 能力边界:AspectJ 支持字段/构造器/静态方法织入,Spring AOP 仅方法

AspectJ 采用静态织入,通过编译器(ajc)在编译期或加载期(Load-time Weaving)直接修改字节码,把切面逻辑织入目标类,因此能代理 final 类、final 方法、字段访问、构造器调用、静态方法/属性等 Spring AOP 无法覆盖的点,且运行时没有额外反射开销,性能更高。Spring AOP 采用运行时织入,通过 JDK 动态代理或 CGLIB 在运行时生成代理对象。二者核心差异:Spring AOP 只支持方法级拦截(且仅限被 Spring 管理的 Bean 通过代理调用的方法),简单且与 Spring 无缝集成;AspectJ 能力更强但需要引入 AspectJ 编译器或 LTW 配置,复杂度高。

Spring AOP 选择的"运行时动态代理"换取的是易用性与集成度,代价是能力边界(不能代理 final、静态、构造器)。对于绝大多数业务场景方法级拦截已足够,只有当需要更底层织入时才引入 AspectJ 静态织入。

#
★★★

14. Bean 的作用域(Singleton/Prototype/Request/Session/Application)

请说明 Spring 中 Bean 的 Singleton、Prototype、Request、Session、Application 五种作用域各自的含义与生命周期?

  • 五种作用域的定义与适用场景
  • Singleton 默认且单例,Prototype 每次新建
  • 异常作用域(Request/Session/Application)仅 Web 容器生效

Singleton(默认)作用域保证容器内同一 Bean 名只有一个实例,随容器创建与销毁,常用于无状态组件(Service、Repository);Prototype 作用域每次 getBean 或注入都会新建实例,容器只负责创建不负责后续销毁,适合有状态或短生命周期对象;Request、Session、Application 作用域仅 Web 环境下有效,前者每个 HTTP 请求一个实例、中者每个 Session 一个实例、后者每个 ServletContext 一个实例。混合作用域时,若 Singleton 注入 Prototype 需通过 @Scope(value=..., proxyMode=ScopedProxyMode.TARGET_CLASS) 使用代理或 ObjectProvider 以确保每次拿到新实例。

作用域决定了 Bean 的实例数与生命周期管理权。默认 Singleton 是性能与一致性的平衡;Prototype 需注意容器不管理其销毁生命周期;Web 作用域常配代理模式解决"单例注入非单例"问题。

@Component
@Scope(value = "prototype")
public class TaskRunner {}

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {}
#
★★★

15. Bean 的生命周期与扩展点(InitializingBean/DisposableBean/BeanPostProcessor)

请说明 Bean 在 Spring 容器中的完整生命周期,以及 InitializingBean、DisposableBean、BeanPostProcessor 等扩展点分别在哪个阶段被调用?

  • Bean 生命周期各阶段(实例化、属性填充、初始化、使用、销毁)
  • BeanPostProcessor 的 postProcessBeforeInitialization/postProcessAfterInitialization
  • InitializingBean/DisposableBean、@PostConstruct/@PreDestroy、init-method

Bean 生命周期主要阶段:实例化(构造对象)→ 属性填充(注入依赖)→ 感知 Aware 回调 → BeanPostProcessor.postProcessBeforeInitialization → 初始化(@PostConstruct、InitializingBean.afterPropertiesSet、init-method 依次执行)→ BeanPostProcessor.postProcessAfterInitialization → 使用 → 销毁(@PreDestroy、DisposableBean.destroy、destroy-method)。其中 BeanPostProcessor 是对所有 Bean 生效的容器级扩展点,可对所有 Bean 统一织入逻辑(如 AOP 代理就在 postProcessAfterInitialization 阶段生成);InitializingBean/DisposableBean 是单 Bean 的初始化/销毁钩子。

生命周期顺序是容器级扩展点(BeanPostProcessor)与单 Bean 级钩子(InitializingBean 等)的嵌套。理解"BeanPostProcessor 作用于全部 Bean、且先于初始化回调"是理解 AOP 代理、@Autowired 等机制的关键。

#
★★

16. BeanDefinition 从注册、合并(MergedBeanDefinition)到属性解析经历了哪些阶段,BeanFactoryPostProcessor 在哪一步介入

请说明 BeanDefinition 从注册、合并(MergedBeanDefinition)到属性解析经历的阶段,以及 BeanFactoryPostProcessor 在哪一步介入?

  • BeanDefinition 注册与合并(父子定义合并)
  • BeanFactoryPostProcessor 在 Bean 实例化前介入
  • 与 BeanPostProcessor 介入时机的区分

BeanDefinition 的处理流程:先是注册阶段,扫描或通过 @Bean/@Component 解析出的 BeanDefinition 被注册进容器;接着是合并阶段,将父 BeanDefinition 的属性合并到子定义形成 MergedBeanDefinition;随后进入 Bean 实例化前的准备阶段,这里 BeanFactoryPostProcessor(如 PropertySourcesPlaceholderConfigurer、ConfigurationClassPostProcessor)被调用,可在 Bean 实例化之前修改 BeanDefinition 的属性、作用域等。之后再按 MergedBeanDefinition 进行属性解析与实例化。关键区别:BeanFactoryPostProcessor 在所有 Bean 实例化之前对 BeanDefinition 生效,而 BeanPostProcessor 在单个 Bean 实例化过程中对 Bean 实例生效。

时序上 BeanFactoryPostProcessor 先于 BeanPostProcessor。前者改"定义"(元数据),后者改"实例"(对象)。Spring 的占位符替换、@Configuration 处理等都是 BeanFactoryPostProcessor 的典型应用。

#
★★

17. BeanPostProcessor 与 BeanFactoryPostProcessor 的执行顺序

请说明 BeanPostProcessor 与 BeanFactoryPostProcessor 的执行顺序,以及二者在容器启动中的职责差异?

  • BeanFactoryPostProcessor 先于 BeanPostProcessor 注册与执行
  • 前者作用于 BeanDefinition,后者作用于 Bean 实例
  • 对 Bean 实例化的影响

容器启动时,所有 BeanFactoryPostProcessor 先被收集并执行,因为它的职责是修改 BeanDefinition(元数据),必须在 Bean 实例化之前完成;随后才是 BeanPostProcessor 的注册,并在每个 Bean 实例化的前后(postProcessBeforeInitialization/postProcessAfterInitialization)被调用。因此严格顺序是:BeanFactoryPostProcessor 全集执行 → Bean 实例化 → 各 Bean 在实例化过程中穿插 BeanPostProcessor。若 BeanPostProcessor 自身也是 Bean,Spring 会先实例化它再让其参与后续 Bean 的加工。

顺序差异源于"先改定义、后改实例"的先后需求。BeanFactoryPostProcessor 是所有 Bean 实例化的前置条件,而 BeanPostProcessor 是每个 Bean 实例化的过程增强点。

#
★★

18. FactoryBean 与普通 Bean 的差异

请说明 FactoryBean 与普通 Bean 的差异,以及 FactoryBean 在 Spring 框架中的应用(如 MyBatis、代理对象)?

  • FactoryBean 是工厂 Bean,getObject 返回实际对象
  • &name 前缀获取 FactoryBean 本身
  • 应用场景:代理对象、MyBatis Mapper、AOP 代理

FactoryBean 是 Spring 提供的一种工厂接口,本身是一个 Bean,但 getObject() 方法返回的是真正提供给容器的对象,可用于复杂对象的创建。普通 Bean 由容器直接实例化,而 FactoryBean 可定制创建逻辑。区分:通过 &beanName 获取 FactoryBean 本身,直接 beanName 获取 getObject() 返回的对象。典型应用:MyBatis 的 MapperFactoryBean 生成 Mapper 代理、AOP ProxyFactoryBean 生成代理对象、及需要封装复杂初始化逻辑的对象。

FactoryBean 把"对象创建"从"容器实例化"中解耦,让 Spring 能无缝集成非标准构造方式的对象(如 MyBatis 接口代理、RMI 代理),是框架集成的关键扩展点。

@Component
public class MyFactoryBean implements FactoryBean<ComplexService> {
    @Override
    public ComplexService getObject() { return new ComplexService(); }
    @Override
    public Class<?> getObjectType() { return ComplexService.class; }
    @Override
    public boolean isSingleton() { return true; }
}
// 获取 FactoryBean 本身: applicationContext.getBean("&myFactoryBean")
#
★★

19. IoC 与 DI 的本质、容器职责与对象生命周期

请说明 IoC(控制反转)与 DI(依赖注入)的本质区别,以及 IoC 容器的职责与它对对象生命周期的管理?

  • IoC:控制权反转,对象创建与依赖由容器管理
  • DI:IoC 的一种实现方式
  • 容器职责:创建、装配、配置、管理生命周期

IoC(控制反转)是一种设计思想,核心是"对象创建与依赖关系的控制权从对象自身反转给外部容器"。DI(依赖注入)是 IoC 的一种实现方式,通过构造器、Setter、字段注入把依赖传递给对象。Spring IoC 容器(ApplicationContext)的职责包括:创建并管理 Bean 实例、解析并注入依赖、管理 Bean 生命周期(作用域、初始化、销毁回调)、提供配置与查找能力。容器承担了对象从创建到销毁的全生命周期管理,使对象之间松耦合、便于测试与替换。

IoC 是"谁控制谁"的哲学问题,DI 是"如何控制"的具体手段。容器把"找依赖"的职责接管,业务对象只声明需要什么,由容器负责提供,从而减少硬编码依赖。

#
★★

20. JDK 25 虚拟线程下 AOP 性能评估

请评估在 JDK 25 虚拟线程(Virtual Threads)环境下,Spring AOP 的性能表现与潜在影响?

  • 虚拟线程与 AOP 代理的关系
  • AOP 的开销来自代理调用与反射,而非线程模型
  • 虚拟线程下避免阻塞线程池

Spring AOP 的性能开销主要来自代理对象的方法分发与反射调用,与线程模型本身无关,因此虚拟线程不会显著改变 AOP 的基准开销。在虚拟线程下,AOP 代理可以正常工作,因为代理是对象层面的机制,与线程调度解耦。潜在影响在于:虚拟线程允许大量并发任务共享一个线程池,若 AOP 通知中做了阻塞操作(如同步 IO、锁竞争),会占用虚拟线程的载体线程,但虚拟线程的挂起/恢复成本远低于平台线程,总体吞吐受影响较小。评估时应关注 AOP 通知本身的复杂度(如事务、日志、鉴权)而非代理层。

虚拟线程优化的是"线程阻塞的成本",AOP 优化的是"横切逻辑的归并",两者正交。真正的性能瓶颈在通知内执行的阻塞调用,虚拟线程可缓解其线程占用,但不会消除 AOP 代理的反射开销。

#
★★

21. OnBeanCondition 在多个候选 Bean 时按类型/名称匹配的策略细节

请说明 OnBeanCondition(@ConditionalOnBean/@ConditionalOnMissingBean)在存在多个候选 Bean 时,按类型和名称匹配的具体策略细节?

  • 按类型匹配所有候选 Bean
  • 按名字匹配 Bean 名称
  • 类型与名称条件组合时的逻辑

OnBeanCondition 评估时,@ConditionalOnBean 的 value 指定类型、name 指定 Bean 名称。当指定类型时,容器会查找所有该类型的 BeanDefinition;当指定名称时,按名称匹配。若同时指定类型和名称,则要求同时满足"存在该类型的 Bean 且名称匹配"才成立。多个候选时,只要存在任意一个匹配的 Bean 即满足条件(存在性判断,而非唯一性)。@ConditionalOnMissingBean 则相反,要求容器中不存在匹配的 Bean 才成立。判断基于 BeanDefinition 类型属性,且对泛型匹配(如泛型参数)存在局限性。

OnBeanCondition 是"存在性"判断而非"唯一性"判断——只要有一个匹配即通过。这决定了它在自动配置中用于"用户已定义 Bean 则跳过自动配置"的扩展机制。

#
★★

22. Spring AOP 与 Spring Modulith 的边界

请说明 Spring AOP 与 Spring Modulith 的边界与关系,Spring Modulith 是否依赖 AOP?

  • Spring Modulith 围绕模块化架构校验
  • 事件发布与 AOP 的关系
  • 两者关注点不同,边界清晰

Spring Modulith 是 Spring 官方提供的模块化架构支持,用于验证模块边界、应用事件驱动设计、模块化测试等,关注的是"项目结构如何组织成模块并保证模块间通信规范"。Spring AOP 关注的是"横切关注点的织入"。两者边界:Spring Modulith 的模块内部事件发布(ApplicationEventPublisher)并非必须依赖 AOP,但 Modulith 的依赖解析与校验在编译期/测试期完成,不依赖运行时代理。即 Spring Modulith 解决"模块怎么划分",AOP 解决"横切逻辑怎么织入",二者可独立使用也可结合(如模块内事务、事件切面)。

区别在于关注维度:Modulith 是架构/模块层,AOP 是横切/方法层。不要在二者之间混淆职责,Modulith 提供模块边界与事件建模,AOP 提供方法级拦截。

#
★★

23. Spring AOP 在 Spring 6.x 中对 Protobuf/Java 17 record 的支持

请说明 Spring AOP 在 Spring 6.x 中对 Protobuf 以及 Java 17 record 类支持的情况与限制?

  • record 类的不可变性对 AOP 的影响(CGLIB 代理)
  • Protobuf 生成类的 final 特性
  • Spring 6.x 中对 record 的支持

Java 17 的 record 类是隐式 final 的,且其组件访问器方法(getter)是 final 的,因此 CGLIB 无法通过继承生成子类来代理 record 类,Spring AOP 无法直接对 record 类型的方法进行代理拦截。Spring 6.x 对 record 有支持,但主要体现在数据绑定(如 Jackson 反序列化 record、参数绑定)而非 AOP 代理。Protobuf 生成的类也是 final 且内部方法多为 final/static,同样难以被 CGLIB 代理。若需对 record/Protobuf 类型做横切,应改为在调用方或通过接口/包装类间接织入,而非直接代理 record 本身。

record 的"不可变 + final"天然与动态代理的"继承覆写"冲突。Spring 6.x 对 record 的支持聚焦于数据访问与绑定能力,而非代理 record 内部方法,这是对"final 无法代理"约束的延伸。

#
★★

24. Spring AOP 的自调用(self-invocation)

请说明 Spring AOP 的自调用(self-invocation)问题及其原因,以及如何解决?

  • 自调用绕过代理导致 AOP 失效
  • 失效范围:事务、缓存、安全、日志
  • 解决方案:注入自身、AopContext、拆分 Bean、AspectJ 织入

自调用指在同一个 Bean 内部通过 this.xxx() 调用另一个被 AOP 代理的方法。由于 this 是目标对象而非代理对象,调用不会经过代理,因此 AOP 通知(事务、缓存、安全等)不会生效。解决方案:将涉及 AOP 的方法拆分到独立 Bean 中通过外部依赖调用;在类内注入自身代理(@Lazy 注入自身或 ObjectProvider);通过 AopContext.currentProxy() 获取当前代理再调用(需 exposeProxy=true);或使用 AspectJ 静态织入直接修改字节码。

自调用失效是"代理机制"的天然副作用——只有通过代理引用调用才被拦截。把握"一定要从代理对象进入"这一原则,即可贯穿理解事务、缓存、安全等所有 AOP 失效的根因。

@Service
public class UserService {
    @Transactional
    public void update() { /* ... */ }

    public void saveAndUpdate() {
        update();          // 自调用,事务失效!
        // 正确:((UserService) AopContext.currentProxy()).update();
    }
}
#
★★

25. Spring Boot 3.5+ 启动流程中 SpringApplication.run() 的 ApplicationContext 实例化与生命周期

请说明 Spring Boot 3.5+ 启动流程中 SpringApplication.run() 是如何创建 ApplicationContext 并驱动其生命周期的?

  • SpringApplication.run() 的完整流程
  • 环境准备、上下文创建、Bean 加载
  • 启动事件与回调(ApplicationStartingEvent、ApplicationReadyEvent 等)

SpringApplication.run() 的流程大致为:先创建 SpringApplication 并收集主类与初始化器,然后创建 Environment 并准备环境(加载配置文件、绑定属性),发布 ApplicationStartingEvent;接着创建 ApplicationContext(根据 web 类型选用 AnnotationConfigServletWebServerApplicationContext 等),注册主配置类与 BeanDefinition 源,执行 ApplicationContextInitializer,发布 PreparedEvent 与 ContextPreparedEvent;随后 refresh 容器(核心:BeanFactoryPostProcessor 处理、Bean 实例化、BeanPostProcessor 介入、AOP 代理生成、内嵌服务器启动、WebServer 初始化),发布 ContextRefreshedEvent;最后执行 ApplicationRunner/CommandLineRunner 并发布 ApplicationReadyEvent,启动完成。若启动失败则发布 ApplicationFailedEvent。

整个流程围绕"环境准备 → 上下文创建 → refresh 刷新 → 完成后回调"展开,ApplicationContext 的 refresh() 是 Spring 容器初始化的核心,内嵌 Web 服务器(Tomcat)也在 refresh 阶段启动。掌握事件发布顺序有助于在启动阶段做自定义钩子。

#
★★

26. TestConfiguration 与测试 Bean 覆盖

请说明 @TestConfiguration 的作用,以及它在测试中如何补充或覆盖 Bean?

  • @TestConfiguration 是测试专用配置类
  • 与 @Configuration 的差异(不被主扫描)
  • 在测试中补充/覆盖 Bean

@TestConfiguration 是测试专用的配置类注解,与 @Configuration 的作用类似,但不会在常规组件扫描中被发现,只在测试类中显式引用时才生效。它可以直接定义在测试类内部(静态内部类)或作为独立类,用于在测试中补充额外的 Bean(如 Mock 的依赖、测试数据源)或覆盖已有 Bean 定义。通过 @Import 或内嵌于测试类声明。注意:@TestConfiguration 内定义的 @Bean 若与主容器 Bean 同名,会覆盖主 Bean(测试优先)。

@TestConfiguration 的核心价值是"只对测试生效、不污染主配置",避免测试配置泄漏到生产环境。它让测试能精准补充或替换 Bean,是切片测试与单元测试装配的重要工具。

@SpringBootTest
@Import(MyTestConfig.class)
class ServiceTest {
    @TestConfiguration
    static class MyTestConfig {
        @Bean
        @Primary
        public DataSource testDataSource() { return new MockDataSource(); }
    }
}
#
★★

27. TransactionInterceptor 与 AOP 的协作

请说明 TransactionInterceptor 与 AOP 的协作机制,它是如何作为环绕通知实现事务管理的?

  • TransactionInterceptor 是 Around 通知
  • 事务开启、提交、回滚的织入点
  • 与 TransactionManager 的协作

TransactionInterceptor 是 Spring 事务的 AOP 驱动核心,它的 invoke 方法是一个环绕通知(Around advice)。当 AOP 代理拦截到 @Transactional 方法时,TransactionInterceptor 会解析事务属性(传播、隔离、回滚规则),调用在 TransactionManager 中获得或创建事务,执行业务方法;方法正常返回则提交事务,抛出异常则根据回滚规则(默认 RuntimeException 回滚)决定回滚或提交,最后清理事务资源。它把"事务控制"从业务代码中剥离,通过 AOP 代理织入,实现了声明式事务。

TransactionInterceptor 是"AOP 与事务"协作的桥梁:AOP 提供拦截点,TransactionInterceptor 提供事务语义,TransactionManager 提供底层实现。三者各司其职,构成声明式事务的完整链路。

#
★★

28. Spring AOP 性能开销的真实基准

请说明 Spring AOP 性能开销的真实基准情况,动态代理是否会导致明显的性能下降?

  • JDK 动态代理与 CGLIB 的基准开销
  • 反射分发成本
  • 实际业务场景中开销占比

基准测试表明,Spring AOP 的额外开销主要来自代理方法调用时的分发与反射,单次调用开销通常在微秒甚至纳秒级,相比数据库访问、网络 IO 等业务耗时占比极小。CGLIB 直接调用子类覆写方法,开销略低于依赖反射的 JDK 动态代理。在真实业务中,AOP 开销占比通常不足 1%,可忽略;真正影响性能的是通知内部逻辑(如事务等待、日志阻塞、鉴权查询)。因此除非在极高频的 CPU 密集调用路径上,否则无需过度优化 AOP 开销。

AOP 的性能问题是"理论存在、实际微小"。基准结论是:相对业务 IO 开销,代理开销可忽略,性能优化应聚焦通知内的业务逻辑而非代理层本身。

#
★★

29. 循环依赖的检测与 @Lazy 解决方案

请说明 Spring 如何检测循环依赖,以及 @Lazy 如何解决循环依赖问题?

  • 三级缓存机制解决循环依赖
  • 循环依赖的检测与异常
  • @Lazy 注入代理延迟初始化

Spring 通过三级缓存(一级 singletonObjects、二级 earlySingletonObjects、三级 singletonFactories)解决构造器注入之外的循环依赖:Bean 实例化后先暴露早期引用(三级缓存工厂),供依赖它的 Bean 引用,从而打破循环。检测方面,若循环依赖无法通过缓存解决(如构造器注入循环),会抛出 BeanCurrentlyInCreationException。@Lazy 方案:在注入点标注 @Lazy,Spring 会注入一个延迟代理对象,当真正调用时才触发生成真实 Bean,从而推迟依赖解析、规避循环依赖(对构造器注入循环也有效)。

三级缓存解决的是"非构造器注入"的循环依赖,@Lazy 通过代理把实例化推迟到首次使用,是更通用的兜底。但循环依赖本质是设计问题,官方推荐用构造器注入并重构避免循环。

@Service
public class A {
    @Lazy
    @Autowired
    private B b;   // 注入 B 的延迟代理,避免构造器循环
}
#
★★

30. 构造器注入、Setter 注入与字段注入(@Autowired)在不可变性、可测试性、循环依赖与容器耦合上的差异,为什么 Spring 官方推荐优先构造器注入,@Resource 与 @Autowired 的解析规则又有何不同

请比较构造器注入、Setter 注入与字段注入(@Autowired)在不可变性、可测试性、循环依赖与容器耦合上的差异,并说明为什么 Spring 官方推荐优先构造器注入,以及 @Resource 与 @Autowired 的解析规则有何不同?

  • 三种注入方式的不可变性、可测试性、循环依赖、耦合度差异
  • 构造器注入的优点(不可变、必填、便于测试)
  • @Resource 按名称、@Autowired 按类型

字段注入(@Autowired 字段)最简洁但不可变、依赖注入容器、难以单测、且容易产生循环依赖;Setter 注入灵活性较好、可选依赖友好,但可变对象无法保证不可变性;构造器注入保证依赖不可变、强制必填、便于构造测试(直接 new 传参)、与容器解耦,同时能暴露循环依赖及时报错。因此 Spring 官方推荐构造器注入(唯一构造器时自动注入)。@Resource 与 @Autowired 的差异:@Resource 按名称(Bean 名)优先解析,其次按类型,是 JSR-250 规范;@Autowired 按类型优先,多候选时按 @Qualifier/名称回退,是 Spring 自身注解。

构造器注入以"不可变 + 必填 + 可测 + 解耦"胜出,是官方倡导的首要方式;字段注入便捷但牺牲了可测试性与不可变性。@Resource 与 @Autowired 的解析方向相反(名称 vs 类型),是解析差异的核心。

#

31. 自动向量化对循环依赖、数据对齐和边界检查有何要求,如何确认生成了 SIMD 指令

请说明 JVM 自动向量化(Auto-vectorization)对循环依赖、数据对齐和边界检查的要求,以及如何确认生成了 SIMD 指令?

  • 自动向量化对循环结构、数据对齐、边界检查的要求
  • 消除循环依赖与越界检查以触发向量化
  • 通过 JIT 日志(-XX:+PrintAssembly)确认 SIMD

JVM 的自动向量化(HotSpot 的 SuperWord 优化)要求循环满足特定条件:循环内无循环携带依赖(loop-carried dependency,即迭代间数据不互相依赖)、访问数组能被对齐、边界检查能被消除(如通过预分配或循环条件保证不越界)。JIT 会尝试把多个标量运算合并为 SIMD 向量指令(如 AVX 的 256 位运算)。确认方法:通过 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 查看生成的汇编中是否出现 vaddps/vmulpd 等向量指令,或使用 -XX:+PrintOptoAssembly、JITWatch 等工具分析。

自动向量化是 JIT 编译器的底层优化,依赖"循环可向量化 + 数据可对齐 + 无越界"三个前提。请求中它与 Spring 循环依赖无关,但这本身是 JVM 优化知识点,需区分"Spring 循环依赖"与"循环携带依赖"两词。

#

32. AOP 与 GraalVM Native Image 的兼容性挑战

请说明 Spring AOP 在 GraalVM Native Image 下遇到的兼容性挑战?

  • 反射与动态代理在 Native Image 中的限制
  • CGLIB 字节码增强在 AOT 下的限制
  • Spring AOT 的遮挡与配置

GraalVM Native Image 采用 AOT 编译,在构建时需要静态分析确定所有可达的类与反射点,而 Spring AOP 依赖运行时动态代理(JDK Proxy 与 CGLIB),这类运行时生成代码与反射在 Native 环境下需要显式配置(reflect-config.json、proxy-config.json、resource-config.json)才能工作。Spring Boot 3 的 AOT 引擎会尝试在构建期提前生成并注册这些元数据,但 CGLIB 的字节码生成在 Native 中受限,通常需要改用接口代理或显式注册。挑战集中在:动态代理类的可达性、反射调用点、以及 CGLIB 增强类在 AOT 下的规避。

Native Image 的"静态 + 构建期"模型与 AOP 的"运行时动态"模型天然冲突。Spring AOT 通过提前收集元数据缓解,但需在编码时避免过度动态反射,以兼容 Native 部署。