当前位置: 首页 > news >正文

【烈日炎炎战后端】Spring(2.1万字)

Spring

        • 1. 讲下Spring框架
        • 2. Spring与SpringMVC的区别
        • 3. Spring与SpringBoot的关系
        • 3. SpringBoot优势
        • 4. Spring 、Spring Boot 和 Spring Cloud 的关系
        • 4. SpringBoot常用注解
        • 5. Spring IOC和AOP
        • 6 Bean生命周期
        • 7. Spring事务及事务传播
        • 8. Spring中的循环依赖

1. 讲下Spring框架

在这里插入图片描述

Spring框架,可以管理web层,业务层,Dao层,持久层,该Spring可以配置各个层的组件(bean),并且维护各个bean之间的关系.
bean元素的作用是,当Spring的框架加载的时候,Spring就会自动创建一个Bean对象,并放入内存.
Spring中的中一个重要概念是IOC(控制反转),它可以将将对象间的依赖关系交给Spring容器,使用配置文件来创建所依赖的对象,由主动创建对象改为了被动方式,实现解耦合.

2. Spring与SpringMVC的区别

  1. Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring.

简单点的话可以将SpringMVC类比于Struts.

  1. Spring可以说是一个管理bean的容器,也可以说是包括很多开源项目的总称,spring mvc是其中一个开源项目.

3. Spring与SpringBoot的关系

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,它的出现简化了使用 Spring 的难度.同时它集成了大量常用的第三方库配置,大部分的 Spring Boot 应用都只需要非常少量的配置代码(基于 Java 的配置),开发者能够更加专注于业务逻辑.
SpringBoot的优点包括可以独立运行,简化了配置,可以实现自动配置,无代码生成以及XML配置,并且可以进行应用监控.

3. SpringBoot优势

已订正

1. Spring Boot 让开发变得更简单

Spring Boot 对开发效率的提升是全方位的,我们可以简单做一下对比:

在没有使用 Spring Boot 之前我们开发一个 web 项目需要做哪些工作:

  • 1)配置 web.xml,加载 Spring 和 Spring mvc
  • 2)配置数据库连接、配置 Spring 事务
  • 3)配置加载配置文件的读取,开启注解
  • 4)配置日志文件
  • n) 配置完成之后部署 tomcat 调试

可能你还需要考虑各个版本的兼容性,jar 包冲突的各种可行性.

2. Spring Boot 使测试变得更简单

Spring Boot 对测试的支持不可谓不强大,Spring Boot 内置了7种强大的测试框架:

  • JUnit: 一个 Java 语言的单元测试框架
  • Spring Test & Spring Boot Test:为 Spring Boot 应用提供集成测试和工具支持
  • AssertJ:支持流式断言的 Java 测试框架
  • Hamcrest:一个匹配器库
  • Mockito:一个 java mock 框架
  • JSONassert:一个针对 JSON 的断言库
  • JsonPath:JSON XPath 库

我们只需要在项目中引入spring-boot-start-test依赖包,就可以对数据库、Mock、 Web 等各种情况进行测试.Spring Boot Test 中包含了我们需要使用的各种测试场景,满足我们日常项目的测试需求.

3. Spring Boot 让配置变得更简单

Spring Boot 让配置变简单,说到这里我们就需要了解一下 Spring Boot 的核心思想:约定优于配置(convention over configuration).也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性.

参考网站:

springboot 的优点和不足:link

使用SpringBoot的优势:link

4. Spring 、Spring Boot 和 Spring Cloud 的关系

已订正

1.Spring

Spring 最初最核心的两大核心功能 Spring IocSpring Aop 成就了 Spring,Spring 在这两大核心的功能上不断的发展,才有了 Spring 事务、Spirng Mvc 等一系列伟大的产品,最终成就了 Spring 帝国,到了后期 Spring 几乎可以解决企业开发中的所有问题.

2. Spring Boot

Spring Boot 是在强大的 Spring 帝国生态基础上面发展而来,发明 Spring Boot 不是为了取代 Spring ,是为了让人们更容易的使用 Spring .所以说没有 Spring 强大的功能和生态,就不会有后期的 Spring Boot 火热, Spring Boot 使用约定优于配置的理念,重新重构了 Spring 的使用,让 Spring 后续的发展更有生命力.

3. Spring Cloud

Spring Cloud 是一系列框架的有序集合.它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署.

4. Spring 、Spring Boot 和 Spring Cloud 的关系

Spring 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包.

根据上面的说明我们可以看出来,Spring Cloud 是为了解决微服务架构中服务治理而提供的一系列功能的开发框架,并且 Spring Cloud 是完全基于 Spring Boot 而开发,Spring Cloud 利用 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案.

综上我们可以这样来理解,正是由于 Spring Ioc 和 Spring Aop 两个强大的功能才有了 Spring ,Spring 生态不断的发展才有了 Spring Boot ,使用 Spring Boot 让 Spring 更易用更有生命力,Spring Cloud 是基于 Spring Boot 开发的一套微服务架构下的服务治理方案.

用一组不太合理的包含关系来表达它们之间的关系.

Spring 、Spring Boot 和 Spring Cloud 的关系:link

4. SpringBoot常用注解

https://www.cnblogs.com/xiaoxi/p/5935009.html

@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean.
@Scope注解 作用域
@Lazy(true) 表示延迟初始化
@Service用于标注业务层组件
@Controller用于标注控制层组件(如struts中的action)
@Repository用于标注数据访问组件,即DAO组件
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
@Scope用于指定scope作用域的(用在类上)
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@DependsOn:定义Bean初始化及销毁时的顺序
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用.如下:@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用
@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配.
@PostConstruct 初始化注解
@PreDestroy 摧毁注解 默认 单例 启动就加载
@Async异步方法调用**@Test的使用 是该方法可以不用main方法调用就可以测试出运行结果,是一种测试方法**

5. Spring IOC和AOP

什么是Spring:
  Spring是一个开源的,轻量级的IOC和AOP容器框架,简化了开发流程,方便了对其他框架的整合

控制反转(Inversion Of Controll):
  将相互依赖对象的创建和协调工作都交由IOC容器来完成,当某个对象需要其他协作对象时,由IOC通过依赖注入(DI, Dependency Injection)的方式提供协作对象,达到只需要关注业务本身逻辑的目的.

IOC源码中的实现思路

  1. 获取 Bean 的定义
    Spring 中有 AnnotationConfigApplicationContext、ClassPathXmlApplicationContext 等类寻找定义的 Bean .

  2. AnnotationConfigApplicationContext通过扫描包来寻找@Controller、@Service之类的注解,这些类都是@Component的派生注解.

  3. 实例化 Bean 并利用反射注入字段或属性

  4. 添加 Bean 到 Map 容器中

面向切面编程(Aspect Oriented Programming):
效果:分离系统中的各种关注点,进一步解耦模块间的相互依赖,提高模块的重用性

应用场景:权限认证、日志、事务、全局事务处理等,几乎业务功能都需要的功能

Spring AOP自动选择JDK动态代理和CGLIB动态代理:
JDK动态代理需要类实现接口,重写类InvocationHandler中的invok方法,来调度目标对象的方法;

CGLIB动态代理在运行时动态的生成某个类的子类(如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的),重写接口MethodInterceptor中的intercept方法,来调度目标对象的方法.

使用AOP方式实现系读写分离

代理模式

Spring中基于AspectJ的AOP开发:
通过Pointcut定义表达式拦截指定的Joinpoint,再使用Advice在指定的位置增强.

6 Bean生命周期

链接:https://www.zhihu.com/question/38597960/answer/77600561

深究Spring中Bean的生命周期

Bean 的生命周期

如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:

  1. Spring启动**,查找并加载需要被Spring管理的bean,进行Bean的实例化**
  2. Bean实例化后对将Bean的引入和值注入到Bean的属性
  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法**(实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的**)
  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入**(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)**
  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )
  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能)
  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用**(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )**
  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

7. Spring事务及事务传播

Spring 并不直接支持事务,只有当数据库支持事务时,Spring 才支持事务。Spring中有声明式编程式(淘汰)事务,声明式事务是基于Spring AOP方式实现的。所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的,是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是可以通过事务传播机制来决定的。

**声明式事务隔离级别:**较SQL标准的四种隔离级别多一个,为使用数据库默认的隔离级别(isolation_default)。

声明式事务传播特性:传播特性是Spring在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策略,且作用于内层的方法上(多个方法的嵌套调用,最外层方法不存在传播).

Spring提供的事务七大传播方式:

  • PROPAGATION_REQUIRED (必须的,默认)– 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS(支持) – 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY (强制)– 支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW (必须要新的) – 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED (不支持)– 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER(从不) – 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED(嵌套) – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

当前是指外层事务。

propagation_required(必须的,默认): 如果外层事务不存在,内层就主动开启事务;否则使用外层事务

propagation_supports(支持) : 如果外层事务不存在,就不使用事务;否则使用外层事务支持当前事务,如果当前没有事务,就以非事务方式执行。

propagation_mandatory(强制) : 如果外层事务不存在,就抛出异常;否则使用外层事务

propagation_requires_new(必须要新的) : 总是开启一个新事务;如果存在外层事务,就将外层事务挂起

propagation_not_supported(不支持) : 总是不开启事务;如果存在外层事务,就将外层事务挂起

propagation_never(从不) :总是不使用事务;如果存在外层事务,就抛出异常.与mandatory相反

propagation_nested(嵌套) :如果外层事务不存在,就主动创建事务;否则创建嵌套的子事务.外层事务若回滚,会带着子事务一同回滚;子事务若回滚,则到 savepoint (即 刚进入 子事务的暂存点),不会影响到外层事务

基本知识

  1. 事务传播机制只适用于不同bean之间方法的调用,如果一个bean中的两个方法互相调用并不会使用到事务传播。
  2. 事务方法里如果抛RuntimeException,则会导致所有相关事务回滚,个别事务传播机制有点特殊,我们下面会讲到。
  3. 事务方法里如果抛Throwable或者Exception,默认不会导致相关事务回滚,一般都会在出异常的地方提交,就有可能出现部分提交的问题。但可以配置rollback-for属性来控制。

不同传播特性间的对比:

https://blog.csdn.net/konaji/article/details/79626818

nestedrequired 外层事务不存在,都会主动创建事务,那他们两者有什么区别?
   required 外内层方法使用的是同一个事务,只要发生异常,外内层都要回滚;nested 内层使用的是外层的子事务,发生异常回滚时,要视情况而定(如上7中解释).

nestedrequires_new 在内层事务异常回滚时,都不会影响到外层事务,那他们两者有什么区别?
  nested使用的是外层事务的子事务,回滚时,会回到对应的 savepoint ,且外层事务的回滚会带动嵌套内层事务回滚;requires_new的外内层使用的是两个独立的事务,内层事务的操作,不会影响外层事务.

8. Spring中的循环依赖

请讲一讲Spring中的循环依赖,我们的回答主要分下面几点

  1. 什么是循环依赖?
  2. 什么情况下循环依赖可以被处理?
  3. Spring是如何解决的循环依赖?

同时本文希望纠正几个目前业界内经常出现的几个关于循环依赖的错误的说法

  1. 只有在setter方式注入的情况下,循环依赖才能解决(
  2. 三级缓存的目的是为了提高效率(

OK,铺垫已经做完了,接下来我们开始正文

什么是循环依赖?

从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样

img

体现到代码层次就是这个样子

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

当然,这是最常见的一种循环依赖,比较特殊的还有

// 自己依赖自己
@Component
public class A {
    // A中注入了A
 @Autowired
 private A a;
}

虽然体现形式不一样,但是实际上都是同一个问题----->循环依赖

什么情况下循环依赖可以被处理?

在回答这个问题之前首先要明确一点,Spring解决循环依赖是有前置条件的

  1. 出现循环依赖的Bean必须要是单例
  2. 依赖注入的方式不能全是构造器注入的方式(很多博客上说,只能解决setter方法的循环依赖,这是错误的)

其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?我们还是用代码说话

@Component
public class A {
// @Autowired
// private B b;
 public A(B b) {

 }
}


@Component
public class B {

// @Autowired
// private A a;

 public B(A a){

 }
}

在上面的例子中,A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的Bean,在启动时就会报出以下错误:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

为了测试循环依赖的解决情况跟注入方式的关系,我们做如下四种情况的测试

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖(循环依赖)均采用setter方法注入
AB相互依赖(循环依赖)均采用构造器注入
AB相互依赖(循环依赖)A中注入B的方式为setter方法,B中注入A的方式为构造器
AB相互依赖(循环依赖)B中注入A的方式为setter方法,A中注入B的方式为构造器

具体的测试代码很简单,我就不放了。从上面的测试结果我们可以看到,不是只有在setter方法注入的情况下循环依赖才能被解决,即使存在构造器注入的场景下,循环依赖依然被可以被正常处理掉。

那么到底是为什么呢?Spring到底是怎么处理的循环依赖呢?不要急,我们接着往下看

Spring是如何解决的循环依赖?

关于循环依赖的解决方式应该要分两种情况来讨论

  1. 简单的循环依赖(没有AOP)
  2. 结合了AOP的循环依赖

简单的循环依赖(没有AOP)

我们先来分析一个最简单的例子,就是上面提到的那个demo

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

通过上文我们已经知道了这种情况下的循环依赖是能够被解决的,那么具体的流程是什么呢?我们一步步分析

首先,我们要知道Spring在创建Bean的时候默认是按照自然排序来进行创建的,所以第一步Spring会去创建A

与此同时,我们应该知道,Spring在创建Bean的过程中分为三步

  1. 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法
  2. 属性注入,对应方法:AbstractAutowireCapableBeanFactorypopulateBean方法
  3. 初始化,对应方法:AbstractAutowireCapableBeanFactoryinitializeBean

这些方法在之前源码分析的文章中都做过详细的解读了,如果你之前没看过我的文章,那么你只需要知道

  1. 实例化,简单理解就是new了一个对象
  2. 属性注入,为实例化中new出来的对象填充属性
  3. 初始化,执行aware接口中的方法,初始化方法,完成AOP代理

基于上面的知识,我们开始解读整个循环依赖处理的过程,整个流程应该是以A的创建为起点,前文也说了,第一步就是创建A嘛!

img

创建A的过程实际上就是调用getBean方法,这个方法有两层含义

  1. 创建一个新的Bean
  2. 从缓存中获取到已经被创建的对象

我们现在分析的是第一层含义,因为这个时候缓存中还没有A嘛!

调用getSingleton(beanName)

首先调用getSingleton(a)方法,这个方法又会调用getSingleton(beanName, true),在上图中我省略了这一步

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级

  1. singletonObjects,一级缓存,存储的是所有创建好了的单例Bean
  2. earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
  3. singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

因为A是第一次被创建,所以不管哪个缓存中必然都是没有的,因此会进入getSingleton的另外一个重载方法getSingleton(beanName, singletonFactory)

调用getSingleton(beanName, singletonFactory)

这个方法就是用来创建Bean的,其源码如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {

            // ....
            // 省略异常处理及日志
            // ....

            // 在单例对象创建前先做一个标记
            // 将beanName放入到singletonsCurrentlyInCreation这个集合中
            // 标志着这个单例Bean正在创建
            // 如果同一个单例Bean多次被创建,这里会抛出异常
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            // ...
            // 省略catch异常处理
            // ...
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 添加到一级缓存singletonObjects中
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

上面的代码我们主要抓住一点,通过createBean方法返回的Bean最终被放到了一级缓存,也就是单例池中。

那么到这里我们可以得出一个结论:一级缓存中存储的是已经完全创建好了的单例Bean

调用addSingletonFactory方法

如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPNVfAVo-1596679003851)(https://mmbiz.qpic.cn/mmbiz_png/tpEILlElskLZVx9XICtHkcNxLxMg0TuXqbInxnU2GDfYVucSNUDj7DosxW2p2TxUwibEOpKGwMdCic2VIVm8ibnNw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)]

在完成Bean的实例化后,属性注入之前Spring将Bean包装成一个工厂添加进了三级缓存中,对应源码如下:

// 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里只是添加了一个工厂,通过这个工厂(ObjectFactory)的getObject方法可以得到一个对象,而这个对象实际上就是通过getEarlyBeanReference这个方法创建的。那么,什么时候会去调用这个工厂的getObject方法呢?这个时候就要到创建B的流程了。

当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),然后反射调用setter方法完成属性注入。

img

因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建Bean,而此时再调用getBean不是为了创建了,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(a)的流程就是这样子了

img

从这里我们可以看出,注入到B中的A是通过getEarlyBeanReference方法提前暴露出去的一个对象,还不是一个完整的Bean,那么getEarlyBeanReference到底干了啥了,我们看下它的源码

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是通过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator也就是说如果在不考虑AOP的情况下,上面的代码等价于:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}

也就是说这个工厂啥都没干,直接将实例化阶段创建的对象返回了!所以说在不考虑AOP的情况下三级缓存有用嘛?讲道理,真的没什么用,我直接将这个对象放到二级缓存中不是一点问题都没有吗?如果你说它提高了效率,那你告诉我提高的效率在哪?

img

那么三级缓存到底有什么作用呢?不要急,我们先把整个流程走完,在下文结合AOP分析循环依赖的时候你就能体会到三级缓存的作用!

到这里不知道小伙伴们会不会有疑问,B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?

答:不会

这个时候我们需要将整个创建A这个Bean的流程走完,如下图:

img

从上图中我们可以看到,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

结合了AOP的循环依赖

之前我们已经说过了,在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关,我们再来看一看getEarlyBeanReference的代码:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

如果在开启AOP的情况下,那么就是调用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,对应的源码如下:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

回到上面的例子,我们对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。img

看到这个图你可能会产生下面这些疑问

  1. 在给B注入的时候为什么要注入一个代理对象?

答:当我们对A进行了AOP代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

  1. 明明初始化的时候是A对象,那么Spring是在哪里将代理对象放入到容器中的呢?

img

在完成初始化后,Spring又调用了一次getSingleton方法,这一次传入的参数又不一样了,false可以理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,所以这里的这个getSingleton方法做的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean可以认为是必定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增加一个后置处理器:

@Component
public class MyPostProcessor implements BeanPostProcessor {
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  if (beanName.equals("a")) {
   return new A();
  }
  return bean;
 }
}

不过,请不要做这种骚操作,徒增烦恼!

  1. 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

答:不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化

  1. 三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:

// A是单例的,mbd.isSingleton()条件满足
// allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足
// isSingletonCurrentlyInCreation:正在在创建A,也满足
// 所以earlySingletonExposure=true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
// 还是会进入到这段代码中
if (earlySingletonExposure) {
 // 还是会通过三级缓存提前暴露一个工厂对象
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

看到了吧,即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中,因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

三级缓存真的提高了效率了吗?

现在我们已经知道了三级缓存的真正作用,但是这个答案可能还无法说服你,所以我们再最后总结分析一波,三级缓存真的提高了效率了吗?分为两点讨论:

  1. 没有进行AOP的Bean间的循环依赖

从上文分析可以看出,这种情况下三级缓存根本没用!所以不会存在什么提高了效率的说法

  1. 进行了AOP的Bean间的循环依赖

就以我们上的A、B为例,其中A被AOP代理,我们先分析下使用了三级缓存的情况下,A、B的创建流程

img

假设不使用三级缓存,直接在二级缓存中

img

上面两个流程的唯一区别在于为A对象创建代理的时机不同,在使用了三级缓存的情况下为A创建代理的时机是在B中需要注入A的时候,而不使用三级缓存的话在A实例化后就需要马上为A创建代理然后放入到二级缓存中去。

对于整个A、B的创建过程而言,消耗的时间是一样的。

综上,不管是哪种情况,三级缓存提高了效率这种说法都是错误的!

总结

面试官:”Spring是如何解决的循环依赖?“

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:

第一步,先获取到三级缓存中的工厂;

第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。

当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

  • A 创建过程中需要 B,于是 A 将自己放到三级缓存里面 ,去实例化 B

  • B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了!

    • 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
  • B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)

  • 然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面

一道思考题

为什么在下表中的第三种情况的循环依赖能被解决,而第四种情况不能被解决呢?

提示:Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖(循环依赖)均采用setter方法注入
AB相互依赖(循环依赖)均采用构造器注入
AB相互依赖(循环依赖)A中注入B的方式为setter方法,B中注入A的方式为构造器
AB相互依赖(循环依赖)B中注入A的方式为setter方法,A中注入B的方式为构造器

相关文章:

  • tcpdump统计http请求
  • 产品经理技能之MRD的笔记之一
  • 【烈日炎炎战后端】消息队列(1.0万字)
  • css笔记:如何让一个div居于页面正中间
  • 【烈日炎炎战后端】Git(0.1万字)
  • R语言 如何为图片添加文字说明(转载)
  • 【烈日炎炎战后端 】MyBatis(0.4万字)
  • Windows Docker的有趣事实
  • RSD和wlwmanifest是什么
  • 【烈日炎炎战后端】Zookeeper(0.5万字)
  • iOS 中runtime的运用原理
  • 【烈日炎炎战后端】Elecsticsearch(1.5万字)
  • TCP协议三次握手和四次挥手
  • 【烈日炎炎战后端】Nginx(0.3万字)
  • ESLint简单操作
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 【面试系列】之二:关于js原型
  • 10个确保微服务与容器安全的最佳实践
  • canvas 绘制双线技巧
  • CSS 提示工具(Tooltip)
  • github从入门到放弃(1)
  • go append函数以及写入
  • Iterator 和 for...of 循环
  • JavaScript服务器推送技术之 WebSocket
  • JWT究竟是什么呢?
  • nginx 负载服务器优化
  • Python利用正则抓取网页内容保存到本地
  • scrapy学习之路4(itemloder的使用)
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • tweak 支持第三方库
  • 测试如何在敏捷团队中工作?
  • 动态规划入门(以爬楼梯为例)
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 计算机在识别图像时“看到”了什么?
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 学习HTTP相关知识笔记
  • zabbix3.2监控linux磁盘IO
  • ​香农与信息论三大定律
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • ###C语言程序设计-----C语言学习(6)#
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (2020)Java后端开发----(面试题和笔试题)
  • (9)STL算法之逆转旋转
  • (Java)【深基9.例1】选举学生会
  • (第一天)包装对象、作用域、创建对象
  • (二)斐波那契Fabonacci函数
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (一)基于IDEA的JAVA基础10
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版