Spring

felix.shao2025-04-06SpringSpring

Spring

概念整合

spring的核心思想说说你的理解

 IOC、DI、AOP。

IOC和AOP是通过什么机制来实现的?

 IOC: 反射、依赖注入、设计模式-工厂模式、容器实现。
 AOP:JDK 动态代理,AOP 代理。

依赖倒置,依赖注入,控制反转分别是什么?

依赖注入了解吗?怎么实现依赖注入的?

如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计

Bean的单例和非单例,生命周期是否一样

Spring bean的作用域有哪些?

在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做

概念

谈谈你对 Spring 的理解

 什么是 Spring?

  • Spring 是一个生态,可以构建 Java 应用所需的一切基础措施。
  • 通常 Spring 指的就是 Spring Framework。

 核心解释

  • Spring 是一个轻量级的开源容器框架。
  • Spring 是为了解决企业级应用开发的业务逻辑和其他各层对象和对象直接的耦合问题。
  • Spring 是一个 IOC 和 AOP 的容器框架

 IOC: 控制反转。
 AOP:面向切面编程。
 容器:包含并管理应用对象的生命周期。
 补充:可以把 Spring 官网的图贴下。

Spring 的优缺点是什么

(1) 优点如下。

  1. 方便解耦,简化开发。
     通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
  2. AOP编程的支持。
     通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。
  3. 声明式事务的支持。
     可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
  4. 方便程序的测试。
     可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
  5. 方便集成各种优秀框架。
     Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
  6. 降低 JavaEE API 的使用难度。
     Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
  7. Java 源码是经典学习范例。
     Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

(2) 缺点如下。

  1. 学习曲线较陡:由于 Spring 框架提供了丰富的功能和模块,对于初学者来说,学习和掌握Spring框架需要花费一定的时间和精力。
  2. 配置复杂:在使用 Spring 框架时,需要进行大量的XML或注解配置,这在一定程度上增加了开发者的工作量和代码的维护成本。
  3. 运行效率:由于 Spring 框架提供了大量的功能和灵活性,会增加一定的运行时开销,导致应用的运行效率相对较低。
  4. 引入了第三方依赖:在使用 Spring 框架时,需要引入一些第三方库来支持其功能,这增加了应用的复杂性和部署的难度。
紧耦合和松耦合有什么区别?如何编写松耦合的代码?

(1)紧耦合。
 紧密耦合是指类之间高度依赖(通过 new 创建对象)。

(2)松耦合。
 松耦合是通过促进单一职责和接口分离、依赖倒置的设计原则来实现。

IOC 是什么?作用、优点是什么?

 参考 什么是 Spring IOC?有什么作用?open in new window

IOC 的实现机制是什么

 简单工厂和反射。详见 IOC 的实现原理—反射与工厂模式open in new window

IOC 和 DI 的区别是什么

 IoC 是一种设计思想,而 DI 是一种具体的实现技术。可以参考 IoC 和 DI 有什么区别?open in new window理解。

@Component、@Controller、@Repository、@Service 有何区别?
  • @Component 是其他注解的原注解。
  • 其他几个注解是为了让代码阅读性更好。
@Autowired 和 @Resource 之间的区别

 @Autowired 和 @Resource 的区别主要体现在以下 5 点。

  • 来源不同。
  • 依赖查找的顺序不同。
  • 支持的参数不同。
  • 依赖注入的用法不同。
  • 编译器 IDEA 的提示不同。

 可以参考 面试突击78:@Autowired 和 @Resource 有什么区别open in new window

单例 Bean 是单例模式吗

 通常来说,单例模式是指在一个 JVM 中,一个类只能构造出来一个对象,有很多方法来实现单例模式,比如懒汉模式,但是我们通常讲的单例模式有一个前提条件就是规定在一个 JVM 中。
 那如果要在两个 JVM 中保证单例呢?那可能就要用分布式锁这些技术,这里的重点是,我们在讨论单例模式时,是要考虑范围的。
&esmp;而 Spring 中的单例 Bean 也是一种单例模式,只不过范围比较小,范围是 beanName,一个 beanName 对应同一个 Bean 对象,不同 beanName 可以对应不同的 Bean 对象(就算是同一个类也是可以的)。

Bean 的实例化和 Bean 的初始化有什么区别

 Spring 在创建一个 Bean 对象时,会先创建出来一个 Java 对象,会通过反射来执行类的构造方法从而得到一个 Java 对象,而这个过程就是 Bean 的实例化。
 得到 Java 对象后,会进行依赖注入,依赖注入之后就会进行初始化了,而 Bean 的初始化就是调用前面创建出来的 Java 对象中特定的方法,比如 Java 对象实现了 InitializingBean 接口,那么初始化的时候就会执行 Java 对象的 afterPropertiesSet(),Spring 只会执行这个方法,并不关心方法做了什么,我们可以在这个方法中去对某个属性进行验证,或者直接给某个属性赋值都是可以的,反正 Bean 的初始化就是执行 afterPropertiesSet() 方法,或者执行自定义初始化的方法(如 init-method)。

Spring 中 Bean 是线程安全的吗

 Spring 本身并没有针对 Bean 做线程安全的处理,所以:

  1. 如果 Bean 是无状态的,那么 Bean 则是线程安全的。
  2. 如果 Bean 是有状态的,那么 Bean 则不是线程安全的。

 另外,Bean 是不是线程安全,跟 Bean 的作用域没有关系,Bean 的作用域只是表示 Bean 的生命周期范围,对于任何生命周期的 Bean 都是一个对象,这个对象是不是线程安全的,还是得看这个 Bean 对象本身。

BeanFactory 和 ApplicationContext 有什么区别

 BeanFactory 和 ApplicationContext 都是 Spring 容器中的两个核心接口,用于管理 Bean 对象的创建、配置、装配和管理。
 BeanFactory 接口是 Spring 的最基本的接口,提供了最简单的容器的实现。它通过配置文件中的 <bean> 标签来实例化和装配 Bean 对象,每次从容器中获取 Bean 时才真正创建对象。
 与 BeanFactory 不同,ApplicationContext 是对 BeanFactory 的拓展,提供了更加复杂的功能和定制化的配置,比如可以支持国际化、资源文件访问、事件发布等。
 与 BeanFactory 不同的是,它在容器创建时就已经将所有的 Bean 对象都实例化和装配好了,提供了更好的性能和可扩展性。进一步来说,BeanFactory 是 Spring 框架的基础设施,提供了最简单的容器服务,主要负责 Bean 的实例化、配置、管理等工作。
 ApplicationContext 是在 BeanFactory 基础上构建的,提供了更多可自动装配的 BeanFactory 接口实现,支持更多的功能,如国际化支持、事件传递、注解驱动等,同时也代表了 Spring 的一个完整整体。
 因此,ApplicationContext 相对于 BeanFactory 来说,功能更加全面强大,但是也比 BeanFactory 占用更多的系统资源。在实际应用中,如果仅仅需要实现 Bean 的实例化和简单的 Bean 的装配,可以使用 BeanFactory 接口;如果需要更多的功能,考虑使用 ApplicationContext 接口。

BeanFactory 和 Factory 有什么区别

 虽然 BeanFactory 和 FactoryBean 都与 Bean 的创建和管理有关,但它们在功能和使用上存在显著的区别。

  • 功能不同:BeanFactory 负责管理和创建 Bean,而 FactoryBean 是一个接口,允许开发者实现定制化的 Bean 创建逻辑。
  • 角色不同:BeanFactory 是 Spring 框架的核心,负责整个 Bean 的管理;而 FactoryBean 是 Bean 的创建方式之一,用于定制化的 Bean 实例化。
  • 使用方式不同:BeanFactory 通常通过配置文件或者注解来实现 Bean 的管理和创建;而 FactoryBean 需要开发者实现自定义的工厂类,提供定制化的 Bean 创建逻辑。

 一些如细节如下。

  • 如 FactoryBean 是懒加载对象(即用的时候才调用 FactoryBean.getBean() 方法)。
  • BeanFactory.getBean("&xx") 可以获取 FactoryBean 对象,否则会获取 FactoryBean.getBean() 返回的对象。
Spring 如何处理线程安全问题

 类似如下。

  1. 如 UserService scope 配置为多例。
  2. 将成员变量放置到 ThreadLocal 中。
  3. 同步锁。
Spring 如何在并发情况下获取不完整的 Bean

 主要原因是如下两个。

  1. getSingleton() 使用了双重检查锁。
  2. 对一级缓存加锁,加锁过程中,Bean 经过一个完整的生命周期,最后移除二、三级缓存,添加对象到一级缓存后才释放锁。

TIP

 为什么一级缓存不加到锁里面?

  • 性能:避免一级创建好的 Bean 阻塞等待。
JDK 动态代理和 CGLIB 动态代理有什么区别

 Spring AOP 中的动态代理主要有两种方式、JDK 动态代理和 CGLIB 动态代理。

    1. JDK 动态代理只提供接口的代理,不支持类的代理。
    • 1.1 JDK 会在运行时为目标类生成一个动态代理类 $proxy*.class。
    • 1.2 该代理类是实现了目标类接口,并且代理类会实现接口所有的方法增强代码。
    • 1.3 调用时通过代理类先去调用处理类进行增强,再通过反射的方式进行调用目标方法。从而实现 AOP。
    1. 如果代理类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。
    • 2.1 CGLIB 的底层是通过 ASM 在运行时动态的生成目标类的一个子类。(还有其他相关类,主要是为增强调用时效率)会生成多个。
    • 2.2 并且会重写父类所有的方法增强代码。
    • 2.3 调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现 AOP。
      • CGLIB 是通过继承的方法做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
      • CGLIB 除了生成目标子类代理类,还有一个 FastClass(路由类),可以(但不是必须)让本类方法调用进行增强,而不会像 JDK 代理那样本类方法调用增强会失效。
    1. JDK 和 CGLIB 的性能,JDK 动态代理生成类速度块,调用慢,CGLIB 生成类速度慢,但后续调用快,在老版本 CGLIB 的速度是 JDK 速度的 10 倍左右,但是实际上 JDK 的速度在版本升级的时候每次都提高很多性能,而 CGLIB 仍止步不前。
    • 3.1 在对 JDK 动态代理与 CGLIB 动态代理的代码实验中看,1W 次执行下,JDK7 及 8 的动态代理性能比 CGLIB 要好 20% 左右。
Spring 源码用到了哪些设计模式
  1. 简单工厂-BeanFactory。
  2. 工厂方法-FactoryBean。
  3. 单例模式-Bean 实例。
  4. 适配器模式-SpringMVC 中的 HandlerAdapter。
  5. 装饰器模式-BeanWrapper。
  6. 代理模式-AOP底层。
  7. 观察者模式-Spring 的事件监听。
  8. 策略模式-excludeFilters、includeFilters。
  9. 模板方法模式-Spring 几乎所有的外接扩展都采用这种模式。
  10. 责任链模式-AOP 的方法调用。

Spring 面试核心

Spring 之 @Configuration 底层原理详解

 @Configuration 注解底层是如何实现的,通过源码咱们可以反推并总结为以下几点。

  • Spring 首先会获取到所有的 beandefenition。
  • ConfigurationClassUtils 类中 checkConfigurationClassCandidate 方法判断是 Full @Configuration 还是 lite @Bean mode。
  • 通过 ConfigurationClassPostProcessor 后置处理器遍历所有的 beandefenition。
  • 将标记了 Full @Configuration 模式的 beandefenition,会对这个类进行 cglib 代理,生成一个代理类,并把这个类设置到 BeanDefenition 的 Class 属性中。
  • 配置类会被 CGLIB 增强(生成代理对象),放进 IoC 容器内的是代理。
  • 对于内部类是没有限制的:可以是 Full 模式或者 Lite 模式。
  • 配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向 IoC 内的那个单例。
  • 需要用这个 Bean 实例的时候,从这个 Class 属性中拿到的 Class 对象进行反射,最终反射出来的是代理增强后的类。
  • 通过 @Configuration 标注类的 Bean,Spring 会先去容器中查看是否有这个 Bean 实例,如果有就返回已有的对象,没有就创建一个,然后放到容器中。

 这篇文章讲的比较好,直接引用了 @Configuration 注解 -【Spring 底层原理】open in new window

Spring IOC 的加载原理

 DEMO 见 SimpleAnnotationCtxMainopen in new window
 详细可参考源码理解(源码分析 2 容器的基本实现3 默认标签的解析5 bean 的加载 小节有详细代码分析步骤),也可根据如上入口 DEBUG 加深理解,以下是一些步骤说明。

    1. new AnnotationConfigApplicationContext()。
    1. 将配置、注解的信息读取,加载为 BeanDefinition 对象,这些信息包括 Bean 的类名、属性、构造函数参数等。
    • 2.1 注意 AnnotationConfigApplicationContext 该步骤的代码,也在 AbstractApplicationContext.invokeBeanFactoryPostProcessors 子逻辑块里面。
    1. 注册 BeanDefinition 到 DefaultListableBeanFactory.beanDefinitionMap 中(bean 为定义态)。
    • 3.1 XmlBeanFactory 注册方法 BeanDefinitionReaderUtils.registerBeanDefinition()。
    • 3.2 AnnotationConfigApplicationContext 注册方法在 AbstractApplicationContext.refresh() 里面的 invokeBeanFactoryPostProcessors(beanFactory) 中,实际注册代码就比较深了,略。
    1. 循环通过 BeanFactory.getBean() 创建 Bean(注意是非懒加载的,可以看 AbstractApplicationContext.getBean() 方法源码)。
    1. 进入 AbstractBeanFactory.doGetBean 获取 Bean。这里会出现经典的循环依赖问题,后续详细介绍。
    • 5.1 DefaultSingletonBeanRegistry.singletonObjects (bean 为成熟态)中获取,拿到则直接返回,结束 5 步骤,拿不到进入 5.2
    • 5.2 实例化 Bean(反射,属性值都为空,纯净态)。
    • 5.3 依赖注入。循环依赖问题:如果此时 A 依赖 B,B 依赖 A,则 getBean(A) 会调用 getBean(B) 且 getBean(B) 又会调用 getBean(A)。
    • 5.4 初始化。进行方法回调,如 @PostConstruct 进行初始化,注意这里在依赖注入后,已经可以拿到依赖注入的对象了。可以在这里进行数据库连接池、线程池的初始化的业务逻辑。
    • 5.5 创建动态代理对象。BeanPostProcessor.postProcessAfterInitialization 里面创建了动态代理 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 触发,对应源码 AbstractAutoProxyCreator.wrapIfNecessarycreateProxy 代码块。
    • 5.6 动态代理对象放入 DefaultSingletonBeanRegistry.singletonObjects 中。

 对比可以理解如下问题,如。

  • Spring 国际化在哪一步处理(AbstractApplicationContext.initMessageSource)。

TIP

 ApplicationContext 和 BeanFactory 区别。

  • 都是容器,是父子关系。
  • ApplicationContext 提供上层程序员用的,功能更加全面,如事件监听、多语言等。
  • BeanFactory 在底层真正干活的。
Spring Bean 的生命周期
 生命周期

 可以参考源码分析部分 5 bean 的加载 理解。以下是核心步骤。未做额外说明,则都在 AbstractAutowireCapableBeanFactory 类中。

    1. 实例化,可以拆为两步说,推断构造方法、实例化。详细如下介绍。
    1. Bean 属性注入。详见 populateBean()。
    1. 初始化。对应代码为 initializeBean,相关步骤如下。
    • 3.1 Aware 相关处理。invokeAwareMethods
      • 3.1.1 ((BeanNameAware) bean).setBeanName(beanName)
      • 3.1.2 ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl)
      • 3.1.3 ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this)
    • 3.2 调用 BeanPostProcessor 的预初始化方法。applyBeanPostProcessorsBeforeInitialization,注意,@PostConstruct 也是初始化前处理。
    • 3.3 调用初始化方法。invokeInitMethods
      • 3.3.1 调用 InitializingBean 初始化方法。((InitializingBean) bean).afterPropertiesSet()
      • 3.3.2 调用自定义初始化方法。invokeCustomInitMethod(beanName, bean, mbd)
    • 3.4 调用 BeanPostProcessor 的初始化后方法。applyBeanPostProcessorsAfterInitialization
    1. Bean 可以使用了。通过 DefaultSingletonBeanRegistry.getSingleton 添加普通对象或代理对象到容器中。
    1. 容器关闭。
    • 5.1 调用 DisposableBean.destroy()。DestructionAwareBeanPostProcessor.postProcessBeforeDestruction 被调用。
    • 5.2 调用自定义销毁方法。没找到代码入口,可以参考 DisposableBeanAdapter.destroy() 代码理解。
 实例化

 实例化代码可以从 AbstractBeanFactory.doGetBean 里面的 AbstractAutowireCapableBeanFactory.createBean 方法开始跟踪。

    1. 实例化准备工作。
    • 1.1 获取子类和父类已经合并的 RootBeanDefinition。详见 AbstractBeanFactory.doGetBean 的 RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName)。
    • 1.2 实例化。如下链路可以找到 BeanUtils.instantiateClassctor.newInstance(argsWithDefaultValues),即反射加载代码。链路如下。
      • 1.2.1 AbstractAutowireCapableBeanFactory.createBean。
      • 1.2.2 AbstractAutowireCapableBeanFactory.doCreateBean。
      • 1.2.3 AbstractAutowireCapableBeanFactory.createBeanInstance。
      • 1.2.4 AbstractAutowireCapableBeanFactory.instantiateBean。
      • 1.2.5 AbstractAutowireCapableBeanFactory.autowireConstructor。推断构造方法。
      • 1.2.6 SimpleInstantiationStrategy.instantiate。
      • 1.2.7 BeanUtilseanUtils.instantiateClass(constructorToUse)。

 如果是非普通对象,则会将代理对象创建,见 getEarlyBeanReference(beanName, mbd, bean))

 Spring 之推断构造方法底层原理详解

 实例化 SimpleInstantiationStrategy.createBeanInstance 里面有推断构造方法底层源码。
 有点复杂,可以看文章理解 Spring 源码篇之推断构造方法open in new window

Spring 优先级处理

 对应源码在 AbstractApplicationContext.refresh() 进去,找到 AbstractApplicationContext.registerBeanPostProcessors()。
 加载顺序如下。

  1. 先注册 @PriorityOrdered 的 Bean。
  2. 再注册 @Ordered 的 Bean。
  3. 后注册其他 Bean。

 这篇文章有图,结合源码理解也挺好,Spring Bean 的完整生命周期(带流程图,好记)open in new window

TIP

 额外有些内容补充如下。

问题集锦-Spring 循环依赖

 手写循环依赖简单代码,我们用一级缓存解决了循环依赖(仅限死循环问题,Spring 处理场景更复杂) SimCircularCtxMainopen in new window

 Spring 如何利用多级缓存解决循环依赖

 用了 3 个 Map 即 3 级缓存处理。1 级缓存 singletonObjects;2 级缓存 earlySingletonObjects;3 级缓存 singletonFactory。

  • 首先尝试从 singletonObjects 里面获取实例,如果获取不到再从 earlySingletonObjects 里面获取。
  • 如果还获取不到,再尝试从 singletonFactories 里面获取 beanName 对应的 ObjectFactory。
  • 然后调用这个 ObjectFactory 的 getObject 来创建 bean,并放到 earlySingletonObjects 里面去,并且从 singletonFactories 里面 remove 掉这个 ObjectFactory(1、2 级缓存互斥,原因大概率是因为初始化完成后生成了代理)。

 一级缓存缓存完全体的 bean 对象。
 二级缓存是解决并发环境下锁性能问题。
 三级缓存是解决 AOP 问题,以及循环依赖问题。保证了二级缓存 getEarlySingletonObjects 获取的也是 AOP 代理对象,这样就和一级缓存对象对应上了。注意预期只有循环依赖的 bean 才进行动态代理,普通的 bean 依然在初始化后才去创建动态代理,这块逻辑对应 AbstractAutoProxyCreator.earlyProxyReferences 代码控制。
 三级缓存见示例代码 SimCircular3Ctxopen in new window
 三级缓存示例代码如下。

public class SimCircular3Ctx {

    private Map<String, BeanDefinition> beanDefinitionMap = new LinkedHashMap<>();

    // 一级缓存,最终完整 bean 对象
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    private Map<String, ObjectFactory> factoriesObjects = new ConcurrentHashMap<>();

    public SimCircular3Ctx() throws Exception {
        // 加载 ioc 容器
        refresh();
    }

    private void refresh() throws Exception {
        // 读取 BeanDefinition
        loadBeanDefinitions();

        // 创建 bean
        finishBeanFactoryInitialization();
    }

    private void loadBeanDefinitions() {
        RootBeanDefinition beanDefinition1 = new RootBeanDefinition(AService.class);
        RootBeanDefinition beanDefinition2 = new RootBeanDefinition(BService.class);
        beanDefinitionMap.put("aService", beanDefinition1);
        beanDefinitionMap.put("bService", beanDefinition2);
    }

    private void finishBeanFactoryInitialization() throws Exception {
        for(String beanName : beanDefinitionMap.keySet()){
            getBean(beanName);
        }
    }

    // 方法如果加 synchronized 会有性能问题,因此有了二级缓存
    public Object getBean(String beanName) throws Exception {
        // 1 获取
        Object bean = getSingleton(beanName);
        if(Objects.nonNull(bean)){
            return bean;
        }
        synchronized (singletonObjects){
            bean = getSingleton(beanName);
            if(Objects.nonNull(bean)){
                return bean;
            }
        }
        // 2 创建
        // 2.1 实例化
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        Object beanInstance = beanClass.getConstructor().newInstance();

        // put 到三级缓存
        // 预期只有循环依赖的 bean 才进行动态代理,普通的 bean 依然在初始化后才去创建动态代理
        factoriesObjects.put(beanName, () -> new JdkProxyBeanPostProcessor().getEarlyBeanReference(beanInstance, beanName));

        // 2.2 依赖注入 @Autowired
        for(Field declaredField : beanClass.getDeclaredFields()){
            if(declaredField.getAnnotation(Autowired.class) != null){
                String name = declaredField.getName();

                // bytype byname
                Object dependBean = getBean(name);

                declaredField.setAccessible(true);
                declaredField.set(beanInstance, dependBean);
            }
        }

        // 2.3 初始化 aop

        // 3 put 到一级缓存
        singletonObjects.put(beanName, beanInstance);
        earlySingletonObjects.remove(beanName);
        return beanInstance;
    }

    private Object getSingleton(String beanName) {
        if(singletonObjects.containsKey(beanName)){
            return singletonObjects.get(beanName);
        }
        // spring 源码锁的都是 singletonObjects
        synchronized (singletonObjects){
            if(earlySingletonObjects.containsKey(beanName)){
                return earlySingletonObjects.get(beanName);
            }
            if(factoriesObjects.containsKey(beanName)){
                ObjectFactory objectFactory = factoriesObjects.get(beanName);
                Object aopObject = objectFactory.getObject();
                // 存二级缓存,防止循环依赖多次执行 ObjectFactory
                earlySingletonObjects.put(beanName, aopObject);
                return aopObject;
            }
        }
        return null;
    }

}
 Spring 二级缓存能否解决循环依赖

 可以,但是就是实例化之后,就要立即创建 AOP 代理了。另外注意下以下一些点。

  • 一级缓存其实就可以解决死循环依赖问题了,但是在多线程环境下要加如 synchronized 锁保证线程安全。
  • 二级缓存是在并发环境下解决锁粒度问题(容器设置为懒加载就会有并发加载的问题)。
 Spring 的单例如何保证线程安全

 用的双重检查锁保证,参考 Demo 理解 SimCircular2Ctxopen in new window

 Spring 是否解决多例 Bean 的循环依赖

 无法解决。如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存。缓存只能在实例化后判断处理。

 Spring 是否解决构造函数中的循环依赖

 无法解决。如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。缓存只能在实例化后判断处理。

问题集锦-Spring AOP
 什么是 AOP、能做什么

 AOP 一般称为面向切面编程,用于将哪些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块(即切面),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
 AOP 主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。

 AOP 的核心概念

 先理解下 AOP 的核心概念。

  • 切点(Join Point)。
  • 切面(Aspect)。
  • 通知(Advice)。
    • 前置通知(Before Advice):在方法执行之前执行的通知。
    • 后置通知(After Advice):在方法执行之后执行的通知,不管方法是否发生异常。
    • 返回通知(After Returning Advice):在方法正常返回后执行的通知。
    • 异常通知(After Throwing Advice):在方法抛出异常时执行的通知。
    • 环绕通知(Around Advice):在方法执行的前后都执行的通知,允许在方法调用之前和之后都添加自定义逻辑。这种通知最为灵活,可以完全控制目标方法的执行流程。
  • 目标对象(Target)。
  • 代理(Proxy)。
    • JDK 动态代理。
    • CGLIB 代理。
  • 织入(Weaving)。
    • 编译时织入。
    • 类加载时织入。
    • 运行时织入。
 Spring AOP 和 Aspect 有什么区别

 参考理解 Spring AOP 和 AspectJ 有什么区别?open in new window

 AOP 的核心原理

 以注解开始 AOP 的代理为例说明。

  1. 可以通过 @EnableAspectJAutoProxy 注解给 Spring 应用开启 AOP 代理。注意 proxyTargetClass 参与了后续是使用 JDK 代理还是 JVM 代理配置,详细逻辑见 DefaultAopProxyFactory。
  2. @EnableAspectJAutoProxy import 了 AspectJAutoProxyRegistrar。Spring 启动时会在 AbstractApplicationContext.refresh() 时通过 this.invokeBeanFactoryPostProcessors(beanFactory) 将 AspectJAutoProxyRegistrar 注入到容器中。
  3. AspectJAutoProxyRegistrar 的 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry) 最终注册了 AnnotationAwareAspectJAutoProxyCreator 对象到容器中。
  4. AnnotationAwareAspectJAutoProxyCreator 的父类中 AbstractAutoProxyCreator 有方法 getEarlyBeanReference 会触发创建代理对象。
  5. BeanPostProcessor.postProcessAfterInitialization 里面创建了动态代理 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 触发,对应源码 AbstractAutoProxyCreator.wrapIfNecessarycreateProxy 代码块。详细在 Spring IOC 的加载原理 小节中分析过,详细往上翻即可。

 概念类可参考理解 AOP(面向切面编程)全面解析open in new window

问题集锦-Spring 事务
 Spring 事务核心概念

 Spring 事务是一系列动作组成,设计较为复杂,我们可以先理解一些概念或对象,对后续理解事务原理有一定帮助。

  • TransactionInfo。事务的信息详情、包含了一系列对象。
    • TransactionAttribute。事务属性。事务的传播机制、事务的回滚异常、隔离级别。
    • TransactionManager。事务管理器,包含了数据源,如 DataSourceTransactionManager。
      • DataSourceTransactionObject。和数据源关联的事务对象。
        • previousIsolationLevel。隔离级别。
        • savepointAllowed。是否允许 savepoint。
        • currentConnection。连接。
        • transactionActive。事务是否活跃。
        • mustRestoreAutoCommit。是否需要重置自动提交。
    • TransactionStatus。事务状态的包装,如 DefaultTransactionStatus。
      • rollbackOnly。是否只读事务。
      • completed。是否完成。
      • newTransaction。是否新开的事务。
      • savepoint。底层数据库支持。
      • transaction。
      • newSynchronization。是否同步。
  • 事务之保存点(savepoint)。通过文章理解 事务之保存点(savepoint)open in new window
  • 事务挂起。通过文章理解 MySQL Spring 框架下事务的挂起工作原理简述open in new window
 Spring 事务是如何拦截、增强的

 Spring 事务有声明式事务和编程式事务处理,这里用 Spring 5.2.18.RELEASE 源码版本分析如下。

    1. 通过注解 @EnableTransactionManagement 开启事务。mode 配置默认为 proxy,代表是会进行后处理增强。该注解主要是用来拦截符合规则的业务类,并进行增强。
    1. @EnableTransactionManagement 会 import TransactionManagementConfigurationSelector 类。
    1. Selector 导入以下配置类。Spring 启动时 AbstractApplicationContext.refresh() 的 this.invokeBeanFactoryPostProcessors(beanFactory) 会执行 TransactionManagementConfigurationSelector 类的 selectImports 方法。
    • 3.1 AutoProxyRegistrar。主要是给 Spring 容器注册一个代理(创建代理对象的代理)的后置处理器。
      • 3.1.1 注册 InfrastructureAdvisorAutoProxyCreator 为 internalAutoProxyCreator. 跟踪会找到 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry),里面有这个逻辑。
      • 3.1.2 InfrastructureAdvisorAutoProxyCreator 的父类 AbstractAutoProxyCreator.getEarlyBeanReference(),这里复用了 Spring aop 代理那块逻辑。详细往上翻。
    • 3.2 ProxyTransactionManagementConfiguration。导入了 Spring 事务的 3 个核心对象。
      • 3.2.1 BeanFactoryTransactionAttributeSourceAdvisor。事务的主要切面(切面的代理)。
      • 3.2.2 TransactionAttributeSource。规则器,切点过滤 advisor 时用到。
      • 3.2.3 TransactionInterceptor。即 getAdvice(),其实就是 TransactionInterceptor。

 其中 BeanFactoryTransactionAttributeSourceAdvisor 是核心切面类,有 TransactionAttributeSource、TransactionInterceptor 两个参数。
 主要关心以下逻辑,详细内容可参考源码分析小节 10 事务 理解。

  • 拦截符合业务规则的类(切点逻辑处理)。对应代码为 AbstractAutoProxyCreator.wrapIfNecessary 的 getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null)。
  • 给其 Bean 创建代理对象(加了 @Transaction 注解方法所在的类)。对应代码为 createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean))。
  • 增强器为 TransactionInterceptor。
    (1) 当为 JdkDynamicAopProxy 时,JdkDynamicAopProxy.invoke 的 advised 为 BeanFactoryTransactionAttributeSourceAdvisor 类,事务增强同 CglibAopProxy。
    (2) 当为 CglibAopProxy 时,CglibAopProxy.intercept 通过 CglibMethodInvocation.proceed() 为类实现动态代理处理,实际对应 ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this) 即 TransactionInterceptor.invoke() 的处理进行事务增强。
 Spring 事务的传播机制与检查点源码分析

 事务传播机制处理源码如下,概念介绍可参考文章理解 Spring 事务传播行为详解open in new window

  • AbstractPlatformTransactionManager.getTransaction。
    • PROPAGATION_MANDATORY:如果当前无事务,则报错。
    • PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED:如果当前无事务,则在需要事务,新事务、嵌套事务隔离时,新启动一个事务执行,如果有老的事务,老的事务会被挂起。
  • AbstractPlatformTransactionManager.handleExistingTransaction。
    • PROPAGATION_NEVER:有事务,报错。
    • PROPAGATION_NOT_SUPPORTED:有事务,会挂起事务,并在无事务环境下执行业务逻辑。
    • PROPAGATION_REQUIRES_NEW:有事务,挂起当前事务,使用新事务。
    • PROPAGATION_NESTED:有事务,分布判断作如下处理。
      • nestedTransactionAllowed 为 false:即不支持嵌套事务,报错。
      • useSavepointForNestedTransaction 为 true: 复用当前事务,使用保存点。
      • useSavepointForNestedTransaction 为 false:有些不支持保存点,如 JTA 事务,此时使用新事务。

 关于挂起事务和恢复事务,相关代码如下。

// AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
  private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {
    SuspendedResourcesHolder suspendedResources = this.suspend(transaction);

    try {
        return this.startTransaction(definition, transaction, debugEnabled, suspendedResources);
    } catch (Error | RuntimeException beginEx) {
        this.resumeAfterBeginException(transaction, suspendedResources, beginEx);
        throw beginEx;
    }
  }

  protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        List<TransactionSynchronization> suspendedSynchronizations = this.doSuspendSynchronization();

        try {
            Object suspendedResources = null;
            if (transaction != null) {
              // ThreadLocal 解绑 
              suspendedResources = this.doSuspend(transaction);
            }

            String name = TransactionSynchronizationManager.getCurrentTransactionName();
            TransactionSynchronizationManager.setCurrentTransactionName((String)null);
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
            Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel((Integer)null);
            boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
            TransactionSynchronizationManager.setActualTransactionActive(false);
            return new SuspendedResourcesHolder(suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
        } catch (Error | RuntimeException ex) {
            this.doResumeSynchronization(suspendedSynchronizations);
            throw ex;
        }
    } else if (transaction != null) {
        Object suspendedResources = this.doSuspend(transaction);
        return new SuspendedResourcesHolder(suspendedResources);
    } else {
        return null;
    }
  }

  private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
      boolean newSynchronization = this.getTransactionSynchronization() != 2;
      // suspendedResources 保存下来,当前事务提交后,会重置使用它
      DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true,
       newSynchronization, debugEnabled, suspendedResources);
      this.doBegin(transaction, definition);
      this.prepareSynchronization(status, definition);
      return status;
  }
}

// DataSourceTransactionManager
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {
  protected Object doSuspend(Object transaction) {
      DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
      txObject.setConnectionHolder((ConnectionHolder)null);
      return TransactionSynchronizationManager.unbindResource(this.obtainDataSource());
  }
}

// TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {
  public static Object unbindResource(Object key) throws IllegalStateException {
      Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
      Object value = doUnbindResource(actualKey);
      if (value == null) {
          throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread");
      } else {
          return value;
      }
  }
}

// AbstractPlatformTransactionManager,执行后,挂起点设置回来
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
  private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {

    } finally {
        this.cleanupAfterCompletion(status);
    }
  }

  private void cleanupAfterCompletion(DefaultTransactionStatus status) {
      status.setCompleted();
      if (status.isNewSynchronization()) {
          TransactionSynchronizationManager.clear();
      }

      if (status.isNewTransaction()) {
          this.doCleanupAfterCompletion(status.getTransaction());
      }

      if (status.getSuspendedResources() != null) {
          if (status.isDebug()) {
              this.logger.debug("Resuming suspended transaction after completion of inner transaction");
          }

          // 挂起点恢复
          Object transaction = status.hasTransaction() ? status.getTransaction() : null;
          this.resume(transaction, (SuspendedResourcesHolder)status.getSuspendedResources());
      }
    }
}
 TransactionSynchronizationManager
public abstract class TransactionSynchronizationManager {
    // key 为 datasource,value 为 ConnectionHolder
    // Why Map? 如一个线程同时连 Mysql、Oracle 等场景。
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
}
 事务嵌套回滚测试

 对比上述逻辑分析,已经很好理解结论了。

@Service
public class CityServiceImpl implements CityService {

    @Autowired
    private CityMapper cityMapper;

    @Transactional
    @Override
    public void insertCity() {
        City city = new City();
        city.setName("长沙1");
        city.setAge(200);

        cityMapper.insertCity(city);

        SpringUtil.getBean(CityService.class).insertCity2();

        // 此时测试,insertCity2 执行未回滚,但是事务1回滚了
        throw new RuntimeException("xxx");
    }

    /**
     * 模拟单线程有2个不同的事务
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertCity2() {
        City city = new City();
        city.setName("长沙2");
        city.setAge(200);

        cityMapper.insertCity(city);
    }

}
Spring 多线程事务,能否保证事务的一致性
  1. Spring 的事务信息是存在 ThreadLocal 中的,所以一个线程永远只能由一个事务。
  2. 所以 Spring 的事务是无法实现事务一致性的。
  3. 可以通过编程式事务,或者通过分布式事务的思路:二阶段提交方式。

 还有一些事务在多线程下的处理,可以关注下面文章。

Spring 事务失效的场景

 参考 Spring 事务失效的 12 种场景open in new window
 补充,Spring 若未正确配置 Configuration 也会导致事务失效(类的 datasource() 实例不唯一,导致事务的 JdbcTemplate 不唯一导致)。

问题集锦-Spring MVC
 Spring MVC 初始化原理

 Spring MVC 示例详见 sample-spring-mvc-demoopen in new window 代码。
 跟着代码解析分析原理如下。

    1. web.xml 配置了 DispatcherServlet 类,其中有个参数设置了 contextConfigLocation。
    • 1.1 Tomcat 容器启动时会调用 DispatcherServlet 父类 HttpServletBean.init() 方法。
    • 1.2 初始化和创建 WebApplicationContext 方法。
      • 1.2.1 FrameworkServlet.initServletBean。
      • 1.2.2 FrameworkServlet.initWebApplicationContext。
      • 1.2.3 FrameworkServlet.createWebApplicationContext。
      • 1.2.4 设置 ConfigLocation 配置,即 wac.setConfigLocation(configLocation)。
      • 1.2.5 添加 Spring MVC ContextRefreshListener 事件。wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
      • 1.2.6 创建好 wac 后,会触发刷新方法,即 AbstractApplicationContext.refresh()。
    • 1.3 DispatcherServlet.onRefresh() 前置处理。
      • 1.3.1 AbstractApplicationContext.finishRefresh()。
      • 1.3.2 SimpleApplicationEventMulticaster.multicastEvent()。
      • 1.3.3 进入 DispatcherServlet.onRefresh() 方法。其从 ContextRefreshListener.onApplicationEvent() 事件监听进来。
    • 1.4 DispatcherServlet.onRefresh(),即主要的处理逻辑。
      • 1.4.1 初始化 MultipartResolver。
      • 1.4.2 初始化 LocaleResolver。
      • 1.4.3 初始化 ThemeResolver。
      • 1.4.4 初始化 HandlerMappings。
      • 1.4.5 初始化 HandlerAdapters。
      • 1.4.6 初始化 HandlerExceptionResolvers。
      • 1.4.7 初始化 RequestToViewNameTranslator。
      • 1.4.8 初始化 initViewResolvers。
      • 1.4.9 初始化 initFlashMapManager。

 至此,Spring 初始化逻辑分析差不多了,再结合工作原理描述下就可以了。

 Spring MVC 工作原理

 工作原理即流程如下。

  1. 客户端发起请求(http)通过 web.xml 找到 DispatchServlet(前端控制器)。
  2. 由 DispatchServlet 控制器通过配置文件(servletName-servlet.xml)寻找到一个或多个 HandlerMapping(映射处理器),找到用于处理请求的 Controller(后端控制器),也有可能是其他处理器,我们用 Controller 处理器说明流程。
  3. DispatchServlet 将请求提交到 Controller
  4. Controller 处理业务逻辑后。
  5. Controller 返回数据 ModelAndVIew 给 DispatchServlet。
  6. DispatchServlet 寻找到一个或多个 ViewResolver(视图解析器),找到 ModelAndVIew 指定的视图。
  7. DispatchServlet 负责将结果返给 View(客户端 JSP 页面),封装 Http。
  8. view 响应页面的 HTTP 请求,返回响应数据,浏览器绘制页面。

 工作原理参考 SpringMVC 工作原理open in new window

为啥加了 Async 注解后,会导致循环依赖解决不了?如果要解决,怎么处理?

 示例 DEMO 见 SpringCircularCtxTestopen in new window
 通过 DEBUG 源码如下,找到报错代码。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {
  public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
    Object result = existingBean;
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
      // DEBUG 条件 processor.getClass().getName().indexOf("AsyncAnnotationBeanPostProcessor") >= 0
      Object current = processor.postProcessAfterInitialization(result, beanName);
      if (current == null) {
        return result;
      }
      result = current;
    }
    return result;
  }

  protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
    // Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
      // exposedObject 经过处理变为了代理对象
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
        // 重点:会进入到这里,代理对象和当前对象不一致,会报错
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
  }
}

 问题的核心点在于 AsyncAnnotationBeanPostProcessors 没有实现 SmartInstantiationAwareBeanPostProcessor,导致 B 在获取 A 的早期对象的时候不会获取到 A 的代理对象,而是获取到 A 的原始对象,因此报错了。
 解决方法是通过懒加载 @Lazy 的方式处理,不过示例代码中懒加载默认不支持,因此先忽略示例代码。
 详细理解可以参考博客 Spring 使用 @Async 出现循环依赖原因以及解决方案open in new window

附录一、参考文献

Last Updated 5/3/2025, 9:40:00 PM