Spring

覆盖 IoC/AOP/Bean 生命周期/事务/Boot 自动配置等高频考点。每道题包含中英双语答案、代码示例、常见误区和风控关联。
中英双语版本,适合英文面试准备。
相关页面: Java基础 | MySQL | Redis | 实时风控引擎

Q1. Spring Bean 的生命周期完整流程是什么?

EN: Walk me through the complete Spring Bean lifecycle.

难度: ★★★★ | 出现频率: 极高(阿里、美团、字节、京东)

Key Terms: BeanDefinition (Bean 定义), BeanFactoryPostProcessor (BeanFactory 后处理器), instantiation (实例化), population (属性填充), Aware interfaces (感知接口), BeanPostProcessor (Bean 后处理器), InitializingBean (初始化 Bean), DisposableBean (销毁 Bean)

答案要点:

  1. BeanDefinition 加载解析:XML/注解/JavaConfig → BeanDefinition 对象
  2. BeanFactoryPostProcessor:修改 BeanDefinition(如 PropertyPlaceholderConfigurer 替换 ${} 占位符)
  3. 实例化(Instantiation):反射调用构造函数创建对象
  4. 属性填充(Population)@Autowired / @Value 依赖注入
  5. Aware 回调:BeanNameAware → BeanFactoryAware → ApplicationContextAware
  6. BeanPostProcessor#postProcessBeforeInitialization@PostConstruct 在此阶段执行
  7. InitializingBean#afterPropertiesSet:自定义 init-method
  8. BeanPostProcessor#postProcessAfterInitialization:AOP 代理在此阶段生成
  9. 使用
  10. 销毁@PreDestroy → DisposableBean#destroy → 自定义 destroy-method

常见误区:

  • @PostConstructafterPropertiesSet 之后执行 → ✅ @PostConstruct 在前(postProcessBeforeInitialization 阶段)
  • ❌ AOP 代理在实例化时生成 → ✅ AOP 代理在 postProcessAfterInitialization 阶段才生成
  • ❌ BeanFactoryPostProcessor 和 BeanPostProcessor 作用相同 → ✅ 前者修改 BeanDefinition,后者修改 Bean 实例
  • ❌ Assuming @PostConstruct runs after afterPropertiesSet → ✅ @PostConstruct executes first (during postProcessBeforeInitialization)
  • ❌ Believing AOP proxies are created during instantiation → ✅ AOP proxies are generated in the postProcessAfterInitialization phase
  • ❌ Confusing BeanFactoryPostProcessor with BeanPostProcessor → ✅ The former modifies BeanDefinitions, the latter modifies Bean instances

延伸追问:

  • BeanPostProcessor 和 BeanFactoryPostProcessor 的区别是什么?
  • 为什么 BeanPostProcessor 会影响所有 Bean 的初始化性能?
  • 如果一个 Bean 的构造函数抛异常,Spring 会怎么处理?
  • What is the difference between BeanPostProcessor and BeanFactoryPostProcessor?
  • Why can BeanPostProcessor affect the initialization performance of all beans?
  • If a bean's constructor throws an exception, how does Spring handle it?

风控关联:

  • 风控服务中大量使用 BeanPostProcessor(如自定义注解校验规则参数),理解生命周期有助于排查 Bean 初始化顺序问题
  • 风控引擎启动时规则 Bean 的加载顺序直接影响服务可用性
  • Risk control services heavily use BeanPostProcessors (e.g., custom annotation-based rule parameter validation); understanding the lifecycle helps troubleshoot Bean initialization order issues
  • The loading order of rule beans at risk engine startup directly impacts service availability
  • 关联 实时风控引擎

English Answer:

The Spring Bean lifecycle starts before the Java object is actually created. First, Spring parses XML, annotations, or JavaConfig into BeanDefinition objects. Then BeanFactoryPostProcessor can modify those definitions, for example replacing ${} placeholders through PropertyPlaceholderConfigurer. After that, Spring instantiates the Bean by invoking its constructor through reflection.

Once the object exists, Spring performs property population, such as injecting dependencies through @Autowired and values through @Value. Then it invokes Aware callbacks in order, such as BeanNameAware, BeanFactoryAware, and ApplicationContextAware, so the Bean can access container metadata if needed.

The initialization phase then begins. BeanPostProcessor#postProcessBeforeInitialization runs first, and @PostConstruct is executed during this stage. Next, Spring calls InitializingBean#afterPropertiesSet and any custom init-method. After initialization, BeanPostProcessor#postProcessAfterInitialization runs, and AOP proxies are typically created in this phase. The Bean is then ready for normal use. During container shutdown, Spring invokes @PreDestroy, then DisposableBean#destroy, and finally any custom destroy-method.


Q2. Spring 如何解决循环依赖?三级缓存的工作原理?

EN: How does Spring resolve circular dependencies? Explain the three-level cache mechanism.

难度: ★★★★★ | 出现频率: 极高(阿里、美团、字节、蚂蚁)

Key Terms: singletonObjects (一级缓存/单例池), earlySingletonObjects (二级缓存/早期引用), singletonFactories (三级缓存/对象工厂), getEarlyBeanReference (获取早期 Bean 引用), AOP proxy (AOP 代理)

答案要点:

  1. 三级缓存
  2. - singletonObjects(一级):完全初始化好的 Bean - earlySingletonObjects(二级):提前暴露的早期 Bean 引用(可能是代理对象) - singletonFactories(三级):ObjectFactory,调用时生成早期引用

  3. 解决流程(A → B → A 循环):
  4. - 创建 A,实例化后放入三级缓存(singletonFactories) - A 属性填充时发现依赖 B,去创建 B - B 属性填充时发现依赖 A,从三级缓存获取 A 的 ObjectFactory,调用 getEarlyBeanReference() 获取早期引用放入二级缓存 - B 初始化完成,放入一级缓存 - A 继续初始化,完成后放入一级缓存,清除二三级缓存

  5. 为什么需要三级缓存而非两级:三级缓存的 ObjectFactory 延迟生成代理对象。如果没有循环依赖,Bean 会在 postProcessAfterInitialization 正常生成代理;只有在被其他 Bean 提前引用时,才通过 ObjectFactory 提前创建代理
  6. 无法解决的情况:构造器注入的循环依赖(因为实例化阶段就需要依赖);prototype 作用域的循环依赖

代码示例:


// Spring 源码核心逻辑简化
Object getSingleton(String beanName) {
    // 一级缓存
    Object bean = singletonObjects.get(beanName);
    if (bean == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (singletonObjects) {
            // 二级缓存
            bean = earlySingletonObjects.get(beanName);
            if (bean == null) {
                // 三级缓存:ObjectFactory
                ObjectFactory<?> factory = singletonFactories.get(beanName);
                if (factory != null) {
                    bean = factory.getObject(); // 可能提前生成 AOP 代理
                    earlySingletonObjects.put(beanName, bean);
                    singletonFactories.remove(beanName);
                }
            }
        }
    }
    return bean;
}

常见误区:

  • ❌ 二级缓存就够了 → ✅ 没有三级缓存,每次都要提前生成代理对象,即使没有循环依赖也需要,破坏了 AOP 的正常流程
  • ❌ Spring 能解决所有循环依赖 → ✅ 构造器注入和 prototype 作用域的循环依赖无法通过三级缓存解决
  • ❌ 三级缓存是为了提高性能 → ✅ 三级缓存的核心目的是延迟代理创建,保证 AOP 语义正确
  • ❌ Believing two-level caching is sufficient → ✅ Without the third level, a proxy would have to be generated eagerly every time, even when there is no circular dependency, breaking the normal AOP flow
  • ❌ Assuming Spring can resolve all circular dependencies → ✅ Constructor injection and prototype-scoped circular dependencies cannot be resolved by the three-level cache
  • ❌ Thinking the third-level cache exists for performance → ✅ The core purpose of the third-level cache is to defer proxy creation, ensuring correct AOP semantics

延伸追问:

  • Spring Boot 2.6+ 为什么默认禁止循环依赖?
  • 构造器注入的循环依赖有没有办法解决?
  • 三级缓存中 ObjectFactory 什么时候会被调用?
  • Why does Spring Boot 2.6+ disable circular dependencies by default?
  • Is there any way to resolve circular dependencies with constructor injection?
  • When exactly is the ObjectFactory in the third-level cache invoked?

风控关联:

  • 风控引擎中多个 Service 互相引用(如 RiskEngine → FeatureService → RuleCacheService)容易产生循环依赖,推荐使用 @Lazy 或事件驱动解耦
  • Multiple services in a risk engine (e.g., RiskEngine → FeatureService → RuleCacheService) can easily create circular dependencies; use @Lazy or event-driven design to decouple
  • 关联 实时风控引擎

English Answer:

Spring resolves circular dependencies for singleton Beans mainly through a three-level cache. The first level, singletonObjects, stores fully initialized singleton Beans. The second level, earlySingletonObjects, stores early Bean references that have been exposed before full initialization, and those references may already be AOP proxies. The third level, singletonFactories, stores ObjectFactory instances that can create an early reference on demand.

For example, if A depends on B and B depends on A, Spring first instantiates A and puts an ObjectFactory for A into the third-level cache. During A's property population, Spring finds that A depends on B, so it starts creating B. When B needs A, Spring cannot get a fully initialized A from the first-level cache, so it looks in the third-level cache, calls the ObjectFactory, invokes getEarlyBeanReference(), and moves the early reference into the second-level cache. B can then finish initialization and move into the first-level cache. After that, A continues initialization, becomes a complete Bean, and Spring clears the second-level and third-level cache entries for A.

The third-level cache is necessary because AOP proxy creation should be delayed. If there is no circular dependency, the proxy can be created normally in postProcessAfterInitialization. Only when another Bean needs an early reference should Spring create the proxy early through the ObjectFactory. This preserves correct AOP semantics. However, this mechanism cannot solve constructor-based circular dependencies, because the dependency is required during instantiation itself, and it also cannot solve circular dependencies between prototype-scoped Beans.


Q3. Spring AOP 的实现原理?JDK 动态代理和 CGLIB 有什么区别?

EN: How does Spring AOP work? Compare JDK dynamic proxy and CGLIB.

难度: ★★★★ | 出现频率: 极高(阿里、美团、字节)

Key Terms: JDK dynamic proxy (JDK 动态代理), CGLIB (CGLIB 字节码代理), InvocationHandler (调用处理器), MethodInterceptor (方法拦截器), proxy target class (代理目标类), @EnableAspectJAutoProxy (启用切面自动代理)

答案要点:

  1. JDK 动态代理:基于接口,Proxy.newProxyInstance() 生成实现接口的代理类。只能代理接口方法。核心是 InvocationHandler.invoke()
  2. CGLIB:基于继承,通过字节码技术生成目标类的子类。不能代理 final 类/final 方法。核心是 MethodInterceptor.intercept()
  3. Spring 默认策略:Spring Boot 2.x 起默认使用 CGLIB(spring.aop.proxy-target-class=true
  4. AOP 核心概念:切面(Aspect)→ 切点(Pointcut)→ 通知(Advice: Before/After/Around/AfterReturning/AfterThrowing)→ 织入(Weaving)

代码示例:


// JDK 动态代理示例
public class JdkProxyDemo implements InvocationHandler {
    private Object target;
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Before advice
        Object result = method.invoke(target, args); // 执行目标方法
        // After advice
        return result;
    }
}

常见误区:

  • ❌ Spring AOP 可以代理任何方法 → ✅ 只能代理 public 方法,且不能代理 final 方法(CGLIB)或非接口方法(JDK 代理)
  • ❌ AOP 代理在编译期生成 → ✅ Spring AOP 是运行时代理(区别于 AspectJ 的编译期织入)
  • ❌ CGLIB 性能一定比 JDK 代理差 → ✅ JDK 8+ 之后 JDK 动态代理性能已大幅优化,两者差距很小
  • ❌ Believing Spring AOP can proxy any method → ✅ It can only proxy public methods, and cannot proxy final methods (CGLIB) or non-interface methods (JDK proxy)
  • ❌ Assuming AOP proxies are generated at compile time → ✅ Spring AOP uses runtime proxies (unlike AspectJ's compile-time weaving)
  • ❌ Assuming CGLIB always has worse performance than JDK proxy → ✅ Since JDK 8+, JDK dynamic proxy performance has improved significantly — the gap is now minimal

延伸追问:

  • AspectJ 和 Spring AOP 的区别是什么?
  • 为什么 Spring Boot 2.x 默认切换到 CGLIB?
  • 如何在运行时判断一个 Bean 是否被 AOP 代理了?
  • What is the difference between AspectJ and Spring AOP?
  • Why did Spring Boot 2.x switch to CGLIB as the default?
  • How can you determine at runtime whether a Bean has been AOP-proxied?

风控关联:

  • 风控系统中的限流(@RateLimit)、权限校验(@RiskCheck)等横切关注点都用 AOP 实现
  • 风控规则执行的耗时统计、异常兜底也通过 AOP 切面统一处理
  • Cross-cutting concerns in risk control systems — rate limiting (@RateLimit), permission checks (@RiskCheck) — are all implemented with AOP
  • Execution time tracking and exception fallback for risk control rules are also handled uniformly via AOP aspects
  • 关联 实时风控引擎

English Answer:

Spring AOP is implemented with runtime proxies. If the target Bean has interfaces and interface-based proxying is used, Spring can create a JDK dynamic proxy through Proxy.newProxyInstance(). The generated proxy implements the target interfaces, and method calls are handled by InvocationHandler.invoke(). The limitation is that it can only proxy interface methods.

CGLIB works differently. It uses bytecode generation to create a subclass of the target class, and method calls are intercepted through MethodInterceptor.intercept(). Because it depends on inheritance, it cannot proxy final classes or final methods. Since Spring Boot 2.x, the default strategy is to use CGLIB because spring.aop.proxy-target-class=true is enabled by default.

The core AOP model is Aspect, Pointcut, Advice, and Weaving. An Aspect defines cross-cutting logic, a Pointcut decides where the logic applies, Advice defines when and how it runs, such as Before, After, Around, AfterReturning, or AfterThrowing, and Weaving means applying the aspect to the target. In Spring AOP, this weaving happens at runtime through proxies, usually during the BeanPostProcessor after-initialization phase.


Q4. Spring 事务传播行为有哪些?什么情况下事务会失效?

EN: What are the Spring transaction propagation behaviors? In what scenarios can a transaction fail to work?

难度: ★★★★★ | 出现频率: 极高(阿里、美团、字节、蚂蚁)

Key Terms: @Transactional (事务注解), propagation (传播行为), REQUIRED (加入当前事务), REQUIRES_NEW (新建独立事务), NESTED (嵌套事务), rollback-for (回滚条件), self-invocation (自调用), proxy (代理)

答案要点:

  1. 7 种传播行为
  2. - REQUIRED(默认):有事务加入,没有新建 - REQUIRES_NEW:总是新建事务,挂起当前事务 - NESTED:嵌套事务(savepoint),外层回滚内层也回滚,内层回滚外层不回滚 - SUPPORTS:有事务加入,没有非事务执行 - NOT_SUPPORTED:非事务执行,挂起当前事务 - MANDATORY:必须在事务中,否则抛异常 - NEVER:必须非事务,否则抛异常

  3. 事务失效的常见场景(面试高频!):
  4. - 自调用(Self-invocation):同类中方法 A 调用方法 B(@Transactional),B 的事务不生效(因为没经过代理对象) - 方法非 public:Spring AOP 只能代理 public 方法 - 异常被 catch:异常被 try-catch 吞掉,事务感知不到异常不会回滚 - rollback-for 未配置:默认只回滚 RuntimeExceptionError,checked exception 不回滚 - 数据库引擎不支持:MySQL MyISAM 不支持事务 - Bean 未被 Spring 管理new 出来的对象不走代理

代码示例:


// 自调用失效示例
@Service
public class OrderService {
    public void createOrder() {
        // ... 业务逻辑
        this.processPayment(); // ❌ 直接调用,事务不生效!
        // 解决方案1:注入自身代理
        // 解决方案2:AopContext.currentService()
        // 解决方案3:拆分到另一个 Service
    }

    @Transactional(rollbackFor = Exception.class)
    public void processPayment() {
        // ...
    }
}

常见误区:

  • @Transactional 加在任何方法上都生效 → ✅ 只有通过代理对象调用的 public 方法事务才生效
  • ❌ Spring 事务默认回滚所有异常 → ✅ 默认只回滚 RuntimeException 和 Error,checked exception 需要 rollbackFor 配置
  • NESTEDREQUIRES_NEW 效果一样 → ✅ NESTED 是 savepoint 机制,外层回滚时内层也会回滚;REQUIRES_NEW 是独立事务
  • ❌ Assuming @Transactional works on any method → ✅ Transactions only work on public methods invoked through the AOP proxy
  • ❌ Believing Spring rolls back all exceptions by default → ✅ Only RuntimeException and Error are rolled back by default; checked exceptions require rollbackFor configuration
  • ❌ Thinking NESTED and REQUIRES_NEW behave the same → ✅ NESTED uses savepoints — outer rollback cascades to inner; REQUIRES_NEW creates a fully independent transaction

延伸追问:

  • @Transactional(readOnly = true) 有什么实际作用?
  • 如果事务方法中调用了远程服务,远程服务失败本地事务会回滚吗?
  • NESTED 传播行为的 savepoint 在不同数据库中的支持情况?
  • What practical effect does @Transactional(readOnly = true) have?
  • If a transactional method calls a remote service and it fails, will the local transaction roll back?
  • How well is the NESTED propagation's savepoint mechanism supported across different databases?

风控关联:

  • 风控决策记录的写入通常需要事务保证一致性
  • NESTED 适用于风控规则执行中部分规则失败不影响整体决策的场景
  • 交易风控的扣款+风控记录写入需要 REQUIRED 传播保证原子性
  • Risk control decision records typically require transactional consistency
  • NESTED is suitable for scenarios where partial rule failures should not affect the overall risk decision
  • Transaction risk control (deduction + risk record) needs REQUIRED propagation to guarantee atomicity
  • 关联 实时风控引擎

English Answer:

Spring defines seven transaction propagation behaviors. REQUIRED is the default: it joins the current transaction if one exists, otherwise it creates a new one. REQUIRES_NEW always creates a new independent transaction and suspends the current one. NESTED creates a nested transaction based on a savepoint: if the outer transaction rolls back, the inner work rolls back too, but an inner rollback does not necessarily roll back the outer transaction. SUPPORTS joins a transaction if one exists and otherwise runs non-transactionally. NOT_SUPPORTED always runs without a transaction and suspends the current one. MANDATORY requires an existing transaction and throws an exception if none exists. NEVER requires that no transaction exists and throws an exception if one is active.

The most common transaction failure scenario is self-invocation. If method A in the same class calls method B directly, and B has @Transactional, the call does not go through the Spring proxy, so the transaction does not take effect. Transactions can also fail when the method is not public, because Spring's proxy-based transaction management mainly applies to public methods. Another common issue is catching and swallowing the exception inside the method, which prevents Spring from seeing the exception and triggering rollback.

Rollback rules are also important. By default, Spring rolls back only for RuntimeException and Error; checked exceptions require explicit rollbackFor configuration. In addition, the database engine must support transactions, for example MySQL MyISAM does not. Finally, the object must be managed by Spring. If the service is created manually with new, the call bypasses the container and no transactional proxy is applied.


Q5. Spring Boot 自动配置原理是什么?

EN: How does Spring Boot auto-configuration work under the hood?

难度: ★★★★ | 出现频率: 极高(阿里、美团、字节)

Key Terms: @SpringBootApplication (启动注解), @EnableAutoConfiguration (启用自动配置), spring.factories (自动配置注册文件), AutoConfiguration.imports (Boot 3.x 配置导入), @Conditional (条件装配), spring-boot-configuration-processor (配置元数据处理器)

答案要点:

  1. @SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
  2. @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 加载 META-INF/spring.factories(Boot 2.x)/ META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Boot 3.x)中的自动配置类
  3. 每个自动配置类通过 @Conditional 系列注解判断是否生效:
  4. - @ConditionalOnClass:classpath 中存在指定类 - @ConditionalOnMissingBean:容器中不存在指定 Bean - @ConditionalOnProperty:配置文件中有指定属性

  5. 自动配置类用 @Configuration + @Bean 注册组件,用 @EnableConfigurationProperties 绑定配置

常见误区:

  • ❌ spring.factories 中列出的所有配置类都会生效 → ✅ 每个配置类通过 @Conditional 条件判断,只有满足条件才会生效
  • ❌ 自动配置类只能通过 spring.factories 加载 → ✅ Boot 3.x 已迁移到 AutoConfiguration.imports 文件
  • ❌ @ComponentScan 能扫描到自动配置类 → ✅ 自动配置类通过专门的 ImportSelector 机制加载,不在组件扫描范围内
  • ❌ Assuming all configuration classes in spring.factories take effect → ✅ Each class is filtered by @Conditional annotations — only those meeting all conditions are activated
  • ❌ Believing auto-configuration classes can only be loaded via spring.factories → ✅ Boot 3.x has migrated to the AutoConfiguration.imports file
  • ❌ Thinking @ComponentScan can pick up auto-configuration classes → ✅ Auto-configuration classes are loaded through a dedicated ImportSelector mechanism, outside the component scan scope

延伸追问:

  • 如何自定义一个 Starter?需要哪些步骤?
  • @ConditionalOnMissingBean 在什么场景下需要注意 Bean 定义顺序?
  • 如何排查某个自动配置类为什么没有生效?
  • How do you create a custom Starter? What steps are required?
  • In what scenarios should you pay attention to Bean definition order when using @ConditionalOnMissingBean?
  • How do you troubleshoot why a particular auto-configuration class is not taking effect?

风控关联:

  • 风控系统的自定义 Starter(如 risk-spring-boot-starter)利用自动配置机制实现开箱即用,其他业务服务只需引入依赖即可接入风控能力
  • A custom risk control Starter (e.g., risk-spring-boot-starter) leverages auto-configuration for out-of-the-box integration — other services only need to add the dependency to enable risk control capabilities
  • 关联 实时风控引擎

English Answer:

Spring Boot auto-configuration starts from @SpringBootApplication, which is a composite annotation combining @SpringBootConfiguration, @EnableAutoConfiguration, and @ComponentScan. The key part is @EnableAutoConfiguration. It uses AutoConfigurationImportSelector to load auto-configuration class names from META-INF/spring.factories in Spring Boot 2.x, or from META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports in Spring Boot 3.x.

Loading an auto-configuration class does not mean it will definitely take effect. Each auto-configuration class is guarded by @Conditional annotations. For example, @ConditionalOnClass checks whether a required class exists on the classpath, @ConditionalOnMissingBean checks whether the application has already defined a Bean, and @ConditionalOnProperty checks whether a configuration property is present or has a specific value.

When the conditions match, the auto-configuration class registers components through @Configuration and @Bean. It can also use @EnableConfigurationProperties to bind external configuration into typed properties classes. This is why a Starter can provide out-of-the-box behavior while still allowing the application to override default Beans.


Q6. MyBatis 的工作原理?一二级缓存的区别?

EN: How does MyBatis work internally? Compare its first-level and second-level caches.

难度: ★★★★ | 出现频率: 极高(阿里、美团、字节)

Key Terms: SqlSession (SQL 会话), Executor (执行器), MappedStatement (映射语句), MapperProxy (Mapper 动态代理), local cache (一级缓存), second-level cache (二级缓存), cache namespace (缓存命名空间)

答案要点:

  1. 核心流程:Mapper 接口 → MapperProxy(动态代理)→ MapperMethodSqlSessionExecutor(Simple/Reuse/Batch)→ StatementHandler → JDBC
  2. 一级缓存(Local Cache):SqlSession 级别,默认开启。同一 SqlSession 中相同查询直接命中缓存。事务提交/关闭时清空。Spring 整合时每次请求新建 SqlSession,一级缓存基本无效
  3. 二级缓存:Mapper namespace 级别,需手动开启(<cache/>)。跨 SqlSession 共享。写操作会清空该 namespace 的缓存。注意:二级缓存存储的是对象的序列化副本,需实现 Serializable

常见误区:

  • ❌ Spring 整合后一级缓存很有用 → ✅ Spring 中每次请求新建 SqlSession,一级缓存基本无效
  • ❌ 二级缓存默认开启 → ✅ 需要手动在 Mapper XML 中添加 <cache/> 配置
  • ❌ 一级缓存和二级缓存存储的是同一个对象引用 → ✅ 二级缓存存储的是序列化副本,需要实现 Serializable 接口
  • ❌ Believing the first-level cache is useful after Spring integration → ✅ Spring creates a new SqlSession per request, so the first-level cache is essentially ineffective
  • ❌ Assuming the second-level cache is enabled by default → ✅ It must be manually enabled by adding <cache/> in the Mapper XML
  • ❌ Thinking both cache levels store the same object reference → ✅ The second-level cache stores serialized copies; entities must implement Serializable

延伸追问:

  • MyBatis 和 Hibernate 的缓存机制有什么本质区别?
  • 多表关联查询时二级缓存可能出现什么数据不一致问题?
  • 如何在生产环境中禁用一级缓存?
  • What are the fundamental differences between MyBatis and Hibernate caching mechanisms?
  • What data inconsistency issues can arise with the second-level cache during multi-table join queries?
  • How can you disable the first-level cache in a production environment?

风控关联:

  • 风控规则配置表的查询适合用二级缓存减少 DB 压力;实时交易数据不适合缓存
  • 风控特征配置读取频繁但变更少,适合利用 MyBatis 缓存优化
  • Risk control rule configuration tables are good candidates for second-level caching to reduce DB pressure; real-time transaction data should not be cached
  • Risk control feature configurations are read frequently but change rarely, making them well-suited for MyBatis cache optimization
  • 关联 实时风控引擎

English Answer:

MyBatis works by creating a dynamic proxy for each Mapper interface. When a mapper method is called, MapperProxy delegates the call to MapperMethod, which uses SqlSession to locate the corresponding MappedStatement. The request then goes through an Executor, such as Simple, Reuse, or Batch, then through StatementHandler, and finally JDBC executes the SQL and maps the result set back to Java objects.

The first-level cache is a local cache scoped to one SqlSession. It is enabled by default, and the same query within the same SqlSession can hit the cache directly. The cache is cleared when the transaction commits or the session closes. In Spring integration, a new SqlSession is usually created for each request or transaction boundary, so the practical value of the first-level cache is limited.

The second-level cache is scoped to a Mapper namespace. It must be enabled manually with <cache/>, and it can be shared across different SqlSessions. Any write operation in the same namespace clears that namespace cache. One important detail is that the second-level cache stores serialized copies rather than the same object reference, so cached entity objects need to implement Serializable.


Q7. @Async 实现原理是什么?线程池怎么配置?

EN: How does @Async work internally? How do you configure the thread pool?

难度: ★★★ | 出现频率: 中高(阿里、美团)

Key Terms: @EnableAsync (启用异步), AsyncAnnotationBeanPostProcessor (异步注解后处理器), TaskExecutor (任务执行器), ThreadPoolTaskExecutor (线程池执行器), RejectedExecutionHandler (拒绝策略)

答案要点:

  1. 原理@EnableAsync 注册 AsyncAnnotationBeanPostProcessor,扫描 @Async 方法并生成 AOP 代理,调用时将任务提交到 TaskExecutor
  2. 默认线程池SimpleAsyncTaskExecutor(不推荐,每次新建线程不回收)。推荐自定义 ThreadPoolTaskExecutor
  3. 自定义配置:通过实现 AsyncConfigurer 接口或定义 TaskExecutor Bean
  4. 注意事项:同 @Transactional 一样,同类自调用的 @Async 不生效

代码示例:


@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("risk-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

常见误区:

  • ❌ @Async 默认使用线程池复用线程 → ✅ 默认使用 SimpleAsyncTaskExecutor,每次新建线程不回收,必须自定义线程池
  • ❌ @Async 方法可以返回任意类型 → ✅ 返回值只能是 void 或 Future/ListenableFuture/CompletableFuture
  • ❌ 同类调用 @Async 方法会异步执行 → ✅ 同 @Transactional 一样,自调用不经过代理,不会异步执行
  • ❌ Assuming @Async uses a thread pool that reuses threads by default → ✅ The default SimpleAsyncTaskExecutor creates a new thread for each task without recycling — a custom thread pool is mandatory
  • ❌ Thinking @Async methods can return any type → ✅ Return values must be void, Future, ListenableFuture, or CompletableFuture
  • ❌ Believing self-invoked @Async methods execute asynchronously → ✅ Like @Transactional, self-invocation bypasses the proxy, so the method runs synchronously

延伸追问:

  • @Async 异常如何处理?调用方能否捕获异步方法的异常?
  • CompletableFuture 和 Future 在 @Async 中有什么区别?
  • 如何为不同的 @Async 方法配置不同的线程池?
  • How are exceptions handled in @Async methods? Can the caller catch exceptions from async methods?
  • What is the difference between CompletableFuture and Future when used with @Async?
  • How do you configure different thread pools for different @Async methods?

风控关联:

  • 风控决策后的异步操作(通知、日志记录、特征回写)使用 @Async + 自定义线程池
  • 风控告警推送、审计日志写入等非核心路径适合异步化,降低主链路延迟
  • Post-decision async operations in risk control (notifications, audit logging, feature write-back) use @Async with a custom thread pool
  • Risk control alert pushes, audit log writes, and other non-critical-path operations are ideal candidates for async processing to reduce main-path latency
  • 关联 实时风控引擎

English Answer:

@Async is enabled by @EnableAsync, which registers AsyncAnnotationBeanPostProcessor. This post-processor scans methods annotated with @Async and creates AOP proxies for them. When the method is invoked through the proxy, the actual work is submitted to a TaskExecutor instead of running on the caller thread.

The default executor is SimpleAsyncTaskExecutor, which is not recommended for production because it creates a new thread for each task and does not reuse threads like a real pool. In production, we should define a ThreadPoolTaskExecutor and configure core pool size, maximum pool size, queue capacity, thread name prefix, and a clear rejection policy. For example, CallerRunsPolicy can provide backpressure by making the caller execute the task when the pool is saturated.

A custom executor can be configured by implementing AsyncConfigurer or by defining a TaskExecutor Bean. The same proxy limitation applies as with @Transactional: if a method in the same class directly calls another @Async method, the call bypasses the proxy, so it executes synchronously and @Async does not take effect.


Q8. Spring 事件机制怎么用?和应用场景?

EN: How does the Spring event mechanism work? What are typical use cases?

难度: ★★★ | 出现频率: 中高(美团、字节)

Key Terms: ApplicationEvent (应用事件), ApplicationListener (应用监听器), @EventListener (事件监听注解), ApplicationEventPublisher (事件发布器), @Async + @EventListener (异步事件监听), @TransactionalEventListener (事务事件监听)

答案要点:

  1. 核心组件:事件(ApplicationEvent)→ 发布者(ApplicationEventPublisher)→ 监听器(@EventListener)
  2. 同步 vs 异步:默认同步。@Async + @EventListener 实现异步
  3. 事务绑定@TransactionalEventListener(phase = AFTER_COMMIT) 只在事务提交后执行

代码示例:


// 风控决策事件示例
public class RiskDecisionEvent extends ApplicationEvent {
    private final String transactionId;
    private final String decision; // PASS/REJECT/REVIEW
    // constructor, getters...
}

@Service
public class RiskEngine {
    @Autowired private ApplicationEventPublisher publisher;

    public Decision evaluate(Transaction tx) {
        Decision d = doEvaluate(tx);
        publisher.publishEvent(new RiskDecisionEvent(this, tx.getId(), d.getResult()));
        return d;
    }
}

@Component
public class RiskAuditListener {
    @Async
    @TransactionalEventListener(phase = AFTER_COMMIT)
    public void onDecision(RiskDecisionEvent event) {
        // 异步写入审计日志,不影响主流程
    }
}

常见误区:

  • ❌ @EventListener 默认异步执行 → ✅ 默认同步,需要配合 @Async 才能异步
  • ❌ @TransactionalEventListener 在事务中同步执行 → ✅ 默认在事务提交后执行(AFTER_COMMIT),不在事务上下文中
  • ❌ Spring 事件机制可以跨应用传播 → ✅ 默认只在同一个 ApplicationContext 内传播,跨应用需要引入消息中间件
  • ❌ Assuming @EventListener executes asynchronously by default → ✅ It is synchronous by default; combine with @Async for async execution
  • ❌ Believing @TransactionalEventListener runs synchronously within the transaction → ✅ It defaults to executing after transaction commit (AFTER_COMMIT), outside the transaction context
  • ❌ Thinking Spring events propagate across applications → ✅ Events only propagate within the same ApplicationContext by default; cross-application propagation requires a message middleware

延伸追问:

  • @EventListener 和 ApplicationListener 接口有什么区别?
  • 如何控制多个监听器的执行顺序?
  • Spring Event 和消息队列(如 Kafka/RocketMQ)在解耦场景下如何选型?
  • What is the difference between @EventListener and the ApplicationListener interface?
  • How do you control the execution order of multiple listeners?
  • How do you choose between Spring Event and message queues (e.g., Kafka/RocketMQ) for decoupling scenarios?

风控关联:

  • 风控决策后的事件驱动处理(审计日志、告警通知、特征回写)用 @TransactionalEventListener 保证事务一致性
  • 风控结果异步通知下游系统(如营销系统冻结权益)通过事件机制解耦
  • Event-driven processing after risk control decisions (audit logs, alert notifications, feature write-back) uses @TransactionalEventListener to ensure transactional consistency
  • Asynchronous notification of risk control results to downstream systems (e.g., freezing marketing benefits) is decoupled via the event mechanism
  • 关联 实时风控引擎

English Answer:

Spring's event mechanism has three core parts: an event object, an ApplicationEventPublisher, and a listener such as @EventListener or ApplicationListener. The publisher publishes an event, and Spring dispatches it to matching listeners inside the same ApplicationContext.

By default, Spring event listeners execute synchronously in the publishing thread. If the listener performs slow work, such as notification, audit logging, or feature write-back, it should be combined with @Async and a properly configured executor to avoid blocking the main business flow.

For transaction-related side effects, @TransactionalEventListener(phase = AFTER_COMMIT) is often safer. It makes the listener run only after the transaction commits, so we do not send notifications or write audit records for operations that are later rolled back. Spring events are suitable for decoupling components inside one application. For cross-service communication, a message broker such as Kafka or RocketMQ is more appropriate.


Q9. Spring Boot 3.x 相比 2.x 有哪些重要变化?

EN: What are the key changes from Spring Boot 2.x to 3.x?

难度: ★★★★ | 出现频率: 中高(字节、阿里、蚂蚁)

Key Terms: Jakarta EE (Jakarta 企业版), GraalVM native image (原生镜像), Spring Framework 6 (Spring 框架 6), Jakarta Servlet (Jakarta Servlet 规范), AOT compilation (提前编译)

答案要点:

  1. 最低要求 JDK 17:升级到 Spring Framework 6
  2. Jakarta EE 迁移javax.*jakarta.*(Servlet、JPA、Validation 等)
  3. GraalVM Native Image 支持:AOT 编译,启动时间从秒级降到毫秒级
  4. Observability 统一:Micrometer + OpenTelemetry 集成
  5. Security 大改WebSecurityConfigurerAdapter deprecated,改用 SecurityFilterChain Bean
  6. 自动配置注册方式变化spring.factoriesAutoConfiguration.imports

常见误区:

  • ❌ 升级 Boot 3.x 只需要改版本号 → ✅ javax 到 jakarta 的包名迁移是最大的破坏性变更,需要全局替换
  • ❌ GraalVM Native Image 开箱即用 → ✅ 需要适配反射、动态代理、资源加载等,部分第三方库可能不兼容
  • ❌ Boot 3.x 完全不兼容 Boot 2.x 的配置 → ✅ 大部分 application.yml 配置项保持兼容,主要是包名和 API 层面的变化
  • ❌ Thinking upgrading to Boot 3.x only requires changing the version number → ✅ The javax-to-jakarta package migration is the most disruptive change and requires a global replacement
  • ❌ Assuming GraalVM Native Image works out of the box → ✅ Reflection, dynamic proxies, and resource loading need adaptation; some third-party libraries may be incompatible
  • ❌ Believing Boot 3.x is completely incompatible with Boot 2.x configurations → ✅ Most application.yml properties remain compatible; changes are mainly at the package name and API level

延伸追问:

  • 如何规划从 Spring Boot 2.x 到 3.x 的迁移方案?
  • GraalVM Native Image 对反射和动态代理有哪些限制?
  • Spring Security 6.x 的新配置方式相比旧版有什么优势?
  • How do you plan a migration from Spring Boot 2.x to 3.x?
  • What limitations does GraalVM Native Image impose on reflection and dynamic proxies?
  • What advantages does the new Spring Security 6.x configuration approach have over the old one?

风控关联:

  • 风控服务如果部署在 Serverless/Knative 环境,Native Image 可以大幅降低冷启动延迟,提升实时风控响应速度
  • Jakarta EE 迁移影响风控系统中所有使用 javax.servlet / javax.validation 的代码
  • If risk control services are deployed in Serverless/Knative environments, Native Image can dramatically reduce cold-start latency, improving real-time risk control response speed
  • The Jakarta EE migration affects all code using javax.servlet / javax.validation in the risk control system
  • 关联 实时风控引擎

English Answer:

Spring Boot 3.x is based on Spring Framework 6 and requires JDK 17 as the minimum runtime. The biggest breaking change is the Jakarta EE migration: many APIs moved from javax.* to jakarta.*, including Servlet, JPA, and Validation. This is usually the most expensive part of a Boot 2.x to 3.x migration.

Boot 3.x also provides stronger GraalVM Native Image support through AOT compilation, which can reduce startup time from seconds to milliseconds and lower memory usage. However, native images require attention to reflection, dynamic proxies, and resource loading, and some third-party libraries may need additional configuration or may not be compatible.

Observability is also more unified in Boot 3.x, with Micrometer and OpenTelemetry integration becoming the standard path for metrics, tracing, and logs. Spring Security changed significantly as well: WebSecurityConfigurerAdapter is deprecated, and applications should define SecurityFilterChain Beans instead. Finally, the auto-configuration registration mechanism changed from spring.factories to AutoConfiguration.imports.


Q10. Spring 循环依赖和 AOP 结合时会出现什么问题?

EN: What problems can arise when circular dependencies combine with AOP?

难度: ★★★★★ | 出现频率: 中(蚂蚁、美团高级岗)

Key Terms: early proxy creation (提前代理创建), getEarlyBeanReference (获取早期 Bean 引用), proxy exposed (代理暴露), BeanCurrentlyInCreationException (Bean 正在创建异常)

答案要点:

  1. 问题:A 循环依赖 B,且 A 需要 AOP 代理。如果提前暴露的引用(从三级缓存获取的)和最终创建的代理对象不一致,Spring 会抛 BeanCurrentlyInCreationException
  2. 原因:三级缓存的 ObjectFactory 调用 getEarlyBeanReference() 生成早期代理,但如果 BeanPostProcessor 在 afterInitialization 又生成了不同的代理,两次引用不一致
  3. Spring 的保护机制AbstractAutoProxyCreatorgetEarlyBeanReferencepostProcessAfterInitialization 中使用缓存,确保同一个 Bean 只创建一次代理
  4. 最佳实践:避免循环依赖(用 @Lazy 延迟注入或重构代码),Spring Boot 2.6+ 默认禁止循环依赖

代码示例:


// @Lazy 解决循环依赖
@Service
public class RiskEngine {
    @Autowired @Lazy
    private FeatureService featureService; // 延迟到首次使用时才创建代理
}

常见误区:

  • ❌ 有三级缓存就不会出问题 → ✅ 如果有多个 BeanPostProcessor 同时创建代理,可能导致早期引用和最终代理不一致
  • ❌ @Lazy 彻底解决了循环依赖 → ✅ @Lazy 只是延迟触发,本质上是绕过了问题而非解决,最佳实践还是重构消除循环依赖
  • ❌ Spring Boot 2.6+ 完全不能用循环依赖 → ✅ 可以通过 spring.main.allow-circular-references=true 开启,但不推荐
  • ❌ Believing the three-level cache prevents all issues → ✅ If multiple BeanPostProcessors create proxies simultaneously, the early reference and the final proxy may be inconsistent
  • ❌ Thinking @Lazy completely solves circular dependencies → ✅ @Lazy only defers the trigger — it bypasses the problem rather than solving it; refactoring to eliminate circular dependencies is the best practice
  • ❌ Assuming Spring Boot 2.6+ completely forbids circular dependencies → ✅ They can be re-enabled via spring.main.allow-circular-references=true, though it is not recommended

延伸追问:

  • Spring Boot 2.6+ 禁止循环依赖的具体实现机制是什么?
  • 除了 @Lazy,还有哪些方式可以解决循环依赖?
  • 如何在代码层面检测潜在的循环依赖问题?
  • What is the specific implementation mechanism by which Spring Boot 2.6+ disables circular dependencies?
  • Besides @Lazy, what other approaches can resolve circular dependencies?
  • How can you detect potential circular dependency issues at the code level?

风控关联:

  • 风控引擎中 RiskEngine、FeatureService、RuleCacheService 等核心 Bean 互相依赖时容易触发此问题,建议使用事件驱动架构解耦
  • 风控系统的启动顺序检测可以通过 @DependsOn 显式控制 Bean 初始化顺序
  • Core beans in the risk engine — RiskEngine, FeatureService, RuleCacheService — often depend on each other, making this issue likely; event-driven architecture is recommended for decoupling
  • Startup order detection in risk control systems can use @DependsOn to explicitly control Bean initialization order
  • 关联 实时风控引擎

English Answer:

When circular dependencies combine with AOP, the main risk is inconsistent proxy references. Suppose A depends on B, B depends on A, and A also needs an AOP proxy. During circular dependency resolution, Spring may expose an early reference to A from the third-level cache. If that early reference is different from the final proxy created in postProcessAfterInitialization, other Beans may hold a different object reference from the one eventually stored in the singleton pool, and Spring can throw BeanCurrentlyInCreationException.

The root cause is that the third-level cache calls getEarlyBeanReference() to create an early proxy when necessary, but a BeanPostProcessor may also try to create a proxy again after initialization. If these two proxy creation paths produce different objects, reference consistency is broken.

Spring's protection mechanism is in AbstractAutoProxyCreator. It caches proxy information across getEarlyBeanReference and postProcessAfterInitialization, so once a proxy has been created early, the same proxy can be reused after initialization instead of creating a second one. The best practice is still to avoid circular dependencies. Use @Lazy only as a workaround for delayed injection, or refactor the design, for example by introducing events or splitting responsibilities. Since Spring Boot 2.6, circular dependencies are disabled by default, although they can be re-enabled with spring.main.allow-circular-references=true.


关联

  • Java基础 — 动态代理是 AOP 的底层实现
  • MySQL — @Transactional 与数据库事务隔离级别配合
  • Redis — Spring Cache 与 Redis 整合
  • 实时风控引擎 — 风控规则执行框架基于 Spring 事件驱动