Spring基础知识汇总

在这里插入图片描述

一、IOC(控制反转)

Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用Java 语言的反射功能实例化,Bean 并建立Bean 之间的依赖关系。Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了Bean 实例缓存、生命周期管理、Bean 实例代理、事件发布、资源装载等高级服务。

  • IOC 可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。

为了更好的理解,以连接数据库为例,先回顾一下传统方式如何实现:

  1. 准备配置文件db.properties
  2. 读取配置信息
  3. 创建DriverManager对象:采用new的方式/反射
  4. 使用对象完成任务

IOC开发模式:创建对象的过程统一交给容器,容器创建好对象数据,程序直接使用。

  1. 先将需要的数据配置到文件中
  2. Spring就会根据配置文件XML/注解,创建对象,添加到容器中,并且可以完成对象直接的依赖【依赖注入 DI】
    假设A类包含B类的方法,需要在配置文件中指定依赖关系
  3. 当需要某个对象实例时,可以直接从容器中获取即可(传统方法中需要程序员自己new,现在是把创建对象交给了容器

Spring会简化new的操作,以及依赖注入

Spring容器结构

Spring主要提供了两种类型的容器:BeanFactory和ApplicationContext。

  • BeanFactory:是基础类型的IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。
  • ApplicationContext:它是在BeanFactory的基础上构建的,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

BeanFactory 是Spring 框架的基础设施,面向Spring 本身;ApplicationContext 面向使用Spring 框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext 而非底层的BeanFactory。

ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. ClassPathXmlApplicationContext:默认从类路径加载配置文件
  2. FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
  3. ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
  4. MessageSource:为应用提供 i18n 国际化消息访问的功能;
  5. ResourcePatternResolver : 所有ApplicationContext 实现类都实现了类似于
    PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载Spring 的配置文件。
  6. LifeCycle:该接口是Spring 2.0 加入的,该接口提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被ApplicationContext 实现及具体Bean 实现, ApplicationContext 会将start/stop 的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。
  7. ConfigurableApplicationContext 扩展于ApplicationContext,它新增加了两个主要的方法: refresh()和close(),让ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。

我们断点查看一下applicationContext的主要属性beanFactory,applicationContext内部维护着beanFactory的引用,如下图示内容:
在这里插入图片描述

beanFactory里面的重要几个属性:

  1. beanDefinitionMap(ioc —> beanFactory —> beanDefinitionMap)
    在这里插入图片描述
  2. singletonObjects(ioc —> beanFactory —> singletonObjects)
    在这里插入图片描述
  3. beanDefinitionName(ioc —> beanFactory —> beanDefinitionName)
    在这里插入图片描述

Spring Bean

Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管理。Spring容器也就是一个 bean工厂(BeanFactory)。应用中bean的实例化,获取,销毁等都是由这个bean工厂管理的。

Spring Bean 作用域

Spring 3 中为Bean 定义了5 中作用域,分别为singleton(单例)、prototype(原型)、request、session 和 global session。

1. singleton:单例模式(多线程下不安全)

默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合。Spring IoC 容器中只会存在一个共享的Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。

2. prototype:原型模式 每次使用时创建

每次通过 Spring 容器获取prototype 定义的 bean 时,容器都将创建一个新的Bean 实例,每个Bean 实例都有自己的属性和状态,而singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
<bean scope="prototype" >设置为多实例机制后, 该 bean 是在 getBean() 时才创建。
通常情况下, lazy-init 就使用默认值 false,在开发看来,用空间换时间是值得的, 除非有特殊的要求。如果 scope="prototype" 这时你的 lazy-init 属性的值不管是 ture,还是 false 都是在 getBean 时候,才创建对象。

3. Request:一次request 一个实例

在一次Http 请求中,容器会返回该Bean 的同一实例。而对不同的Http 请求则会产生新的Bean,而且该bean 仅在当前Http Request 内有效,当前Http 请求结束,该bean实例也将会被销毁。

4. session

在一次Http Session 中,容器会返回该Bean 的同一实例。而对不同的Session 请求则会创建新的实例,该bean 实例仅在当前Session 内有效。同Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session 请求内有效,请求结束,则实例将被销毁。

5. global Session

在一个全局的Http Session 中,容器会返回该Bean 的同一个实例,仅在使用 portlet context 时有效。

Spring Bean 生命周期

在这里插入图片描述

  1. 实例化,IOC依赖注入
    实例化一个Bean,按照Spring 上下文对实例化的Bean 进行配置。
  2. 执行 set 相关方法 (setBeanName)
    如果这个Bean 已经实现了BeanNameAware 接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring 配置文件中Bean 的id 值,对Bean对象进行赋值。
  3. postProcessBeforeInitialization 接口实现-初始化预处理
    如果这个Bean 关联了BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是Bean 内容的更改,并且由于这个是在Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。
  4. 调用 bean 的初始化的方法 init-method(需要配置)
    <bean id="" class="" init-method="初始化方法" destroy-method="销毁方法">
  5. postProcessAfterInitialization
    如果这个Bean 关联了BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。
    注:以上工作完成以后就可以应用这个Bean 了,那这个Bean 是一个Singleton 的,所以一般情况下我们调用同一个id 的Bean 会是在内容地址相同的实例,当然在Spring 配置文件中也可以配置非Singleton。
  6. 使用 bean
  7. Destroy 过期自动清理阶段
    当Bean 不再需要时,会经过清理阶段,如果Bean 实现了DisposableBean 这个接口,会调用那个其实现的destroy()方法;
  8. 当容器关闭时候,调用 bean 的销毁方法destroy-method(需要配置)
    自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。

bean的后置处理器

  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码

简单来说就是一个对象,后置处理器需要实现接口 BeanPostProcessor ,重写里面的两个方法postProcessBeforeInitializationpostProcessAfterInitialization

bean生命周期测试

@Test
public void beanLife() {
   ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
   Student student = ioc.getBean("student", Student.class);
   System.out.println(student);
   // 容器关闭 ==》 bean对象销毁
   ((ConfigurableApplicationContext) ioc).close(); //ApplicationContext接口中没有close()方法,所以转换类型
}

输出结果:
Student的setsId方法被调用
Student的setName方法被调用
postProcessBeforeInitialization
Student被初始化
postProcessAfterInitialization
使用com.shiqi.spring.bean.Student@2898ac89
Student被销毁

DI 依赖注入

在具体的实现中,主要有基于XML配置Bean三种注入方式:

  1. 构造方法注入
    就是被注入对象可以在它的构造方法中声明依赖对象的参数列表,让外部知道它需要哪些依赖对象。然后,IoC Service Provider会检查被注入的对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。
/*带参数,方便利用构造器进行注入*/
public CatDaoImpl(String message){
	this. message = message;
}
// XML配置文件中内容
<bean id="CatDaoImpl" class="com.CatDaoImpl">
	<constructor-arg value=" message "></constructor-arg>
</bean>
  1. setter方法注入
    通过setter方法,可以更改相应的对象属性。所以,当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些,可以在对象构造完成后再注入。
public class Id {
	private int id;
	public int getId() { 
		return id; 
	}
	public void setId(int id) { 
		this.id = id; 
	}
}

<bean id="id" class="com.id "> 
	<property name="id" value="123"></property> 
</bean>
  1. 接口注入
    相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。相对于前两种依赖注入方式,接口注入比较死板和烦琐。

总体来说,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使用最多的注入方式。而接口注入因为侵入性较强,近年来已经不流行了。

基于 XML 配置 bean 的自动装配

5 种不同方式的自动装配:

  1. no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
  2. byName:通过参数名自动装配,Spring 容器在配置文件中发现bean 的autowire 属性被设置成byname,之后容器试图匹配、装配和该bean 的属性具有相同名字的bean。
  3. byType:通过参数类型自动装配,Spring 容器在配置文件中发现bean 的autowire 属性被设置成byType,之后容器试图匹配、装配和该bean 的属性具有相同类型的bean。如果有多个bean 符合条件,则抛出错误。
  4. constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  5. autodetect:首先尝试使用constructor 来自动装配,如果无法工作,则使用byType 方式。
<!-- 基于 xml 的 bean 的自动装配 演示 autowire="byType" 表示根据类型进行自动组装 IOC 容器中只能有一个对应的类型-->
<!-- <bean id="orderAction" autowire="byType" class="com.hspedu.spring.action.OrderAction" />-->
<!-- <bean id="orderService" autowire="byType" class="com.hspedu.spring.service.OrderService"/>-->
<!-- <bean id="orderDao" class="com.hspedu.spring.dao.OrderDao"/>-->

<!-- 基于 xml 的 bean 的自动装配 演示
	特别说明: autowire = "byName" 会自动去找 id 为 setXxxx 后面 Xxxx 的 bean 自动组装,如果找到就装配,如果找不到就报错, 比如这里的 <bean id="orderAction" autowire="byName" class="com.hspedu.bean.OrderAction" /> 就 会 去 找 OrderAction 类 中 定 义 的 setOrderService 的 id 为 orderService 的OrderService 或者【 OrderAction 类 中 定 义 的 setOrderService2 的 id 为 orderService2 的 OrderService】bean 组装,找到就阻装,找不到就组装失败
-->
<bean id="orderAction" autowire="byName" class="com.hspedu.spring.action.OrderAction"/>
<bean id="orderService2" autowire="byName" class="com.hspedu.spring.service.OrderService"/>
<bean id="orderDao" class="com.hspedu.spring.dao.OrderDao"/>

基于注解配置bean

必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间

使用在类上用于实例化的注解的形式有:

  1. @Component 表示当前注解标识的是一个组件—-通用标识
  2. @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
  3. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
  4. @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类
  5. @Bean

使用在字段上用于根据类型依赖注入:

  • @Autowired
  • @Resource

可以被IOC容器扫描到,并创建对象

<!--
    1. 导入 context 名称空间
    2. base-package 指定要扫描的包
    3. 当容器初始化时,就会扫描包下所有有注解的类@Controller / @Service / @Respository / @Component,将其实例化,生成对象,放到容器中
-->
    <context:component-scan base-package="com.shiqi.spring.component"/>

如果不配置xml文件,会报错NoSuchBeanDefinitionException

注意事项:

  1. Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。但是,在SpringMVC中是有区别的。
  2. 默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的 value 属性指定 id 值,并且 value 可以省略。@Component(value = "component01") 默认在容器中的id是myComponent,设置value后id是component01。
自动装配 @Autowired 和 @Resource 的区别
  1. @Autowired是Spring提供的注解,@Resource是JDK提供的注解。
  2. @Autowired是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。
  3. @Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。需要注意的是,@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

二、AOP(切面编程)

AOP (Aspect Orient Programming),直译过来就是面向切面编程。AOP 是一种编程思想,是面向对象编程的—种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

AOP采用的是横向切面的方式,它能够在程序运行期间动态地将代码切入到原有代码的流程中,从而实现横向关注点的处理,如日志记录、事务管理和安全检查等。在Spring中,AOP是通过代理模式来实现的,在创建Bean时为Bean生成一个代理对象,代理对象能够拦截指定方法的调用,并在执行前或执行后执行特定操作。

AOP 核心概念

1、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
3、连接点(joinpoint):被拦截到的点,因为Spring 只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut):对连接点进行拦截的定义
5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象:代理的目标对象
7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
在这里插入图片描述


/**
 * 使用切面编程来替代原来的动态代理类
 *
 * @Aspect 表示这个类是一个切面类
 * @Component 加入到IOC容器中
 * @Before
 * @AfterReturning
 * @AfterThrowing
 * @After
 * @Around
 */

@Aspect
@Component
public class MyAspect {

		//=====AOP-切入点表达式重用 start ======
		/*
		* 这样定义的一个切入点表达式,就可以在其它地方直接使用
		*/
		@Pointcut(value = "execution(public float com.hspedu.spring.aop.joinpoint.SmartDog.getSum(float, float))")
		public void myPointCut() {
		}

    // 在调用目标方法之前
    @Before(value = "execution(public void com.shiqi.spring.aop.aspectj.A.f1())")
		// @Before(value = "myPointCut()")
    public void runBeforeLog(JoinPoint joinPoint) {
        System.out.println("==========前置通知=========");
				joinPoint.getSignature().getName(); // 获取目标方法名
        joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
        joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
        joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
        joinPoint.getTarget(); // 获取被代理的对象
        joinPoint.getThis(); // 获取代理对象自己
    }

    @AfterReturning(value = "execution(public void A.f1())")
    public void runSuccessEndLog(JoinPoint joinPoint) {
        System.out.println("==========方法正常结束通知=========");
    }

    @AfterThrowing(value = "execution(public void A.f1())")
    public void runExceptionLog(JoinPoint joinPoint) {
        System.out.println("==========运行异常通知=========");
    }

    @After(value = "execution(public void A.f1())")
    public void runFinallyLog(JoinPoint joinPoint) {
        System.out.println("==========最终通知=========");
    }

		//=====环绕通知 start=====
    @Around(value = "execution(public int A.f1())")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        String methodName = joinPoint.getSignature().getName();
        try {
            //1.相当于前置通知完成的事情
            Object[] args = joinPoint.getArgs();
            System.out.println("AOP 环绕通知" + methodName + "方法开始了");
            //在环绕通知中一定要调用 joinPoint.proceed()来执行目标方法
            result = joinPoint.proceed();
            //2.相当于返回通知完成的事情
            System.out.println("AOP 环绕通知" + methodName + "方法结束了--结果是:" + result);
        } catch (Throwable throwable) {
            //3.相当于异常通知完成的事情
            System.out.println("AOP 环绕通知" + methodName + "方法抛异常了--异常对象:" + throwable);
        } finally {
            //4.相当于最终通知完成的事情
            System.out.println("AOP 后置通知" + methodName + "方法最终结束了...");
        }

        return result;
    }
}

AOP切入点表达式
语法:execution(public void com.shiqi.spring.aop.aspectj.A.f1())

execution([权限修饰符] [返回值类型] [简单类名/全类名].[方法名]([参数列表]))

  1. 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
  2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
  3. 切入表达式也可以对没有实现接口的类,进行切入

AOP 实现方式

【AspectJ AOP静态代理】AspectJ

AspectJ静态AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的AOP代理类(生成的*. class 文件已经被改掉了,需要使用特定的编译器)。

默认的策略是如果目标类是接口,则使用JDK 动态代理技术,否则使用Cglib 来生成代理。

【Spring AOP 动态代理】JDK的JDKProxy

JDK动态代理又叫接口代理,它是基于Java反射机制实现的,要求目标对象必须实现至少一个接口。所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

【Spring AOP 动态代理】Spring的CGlib

CGLIB代理则是基于字节码操作实现的,当目标对象没有实现接口时,它就会使用CGLIB代理。

通常情况下,Spring默认使用JDK动态代理来实现AOP,但如果目标对象没有实现任何接口,则会自动切换到使用CGLIB代理。

  • JDK 动态代理主要涉及到java.lang.reflect 包中的两个类:InvocationHandler接口和Proxy类,InvocationHandler 通过实现该接口定义横切逻辑,并通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。 CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

面试题:Spring AOP 和 AspectJ AOP 有什么区别?

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

三、面试题

1. Spring循环依赖及解决方式

在这里插入图片描述

什么是循环依赖

循环依赖可以分为直接循环依赖和间接循环依赖,直接循环依赖的简单依赖场景:Bean A 依赖于 Bean B,然后 Bean B 又反过来依赖于 Bean A(Bean A -> Bean B -> Bean A),间接循环依赖的一个依赖场景:Bean A 依赖于 Bean B,Bean B 依赖于 Bean C,Bean C 依赖于 Bean A,中间多了一层,但是最终还是形成循环(Bean A -> Bean B -> Bean C -> Bean A)。

Spring 引入了三级缓存来处理循环依赖问题,三级缓存分别指:

  • 第一级缓存 singletonObjects 用于存放完全初始化好的 Bean,从该缓存中取出的 Bean 可以直接使用
  • 第二级缓存 earlySingletonObjects 用于存放提前暴露的单例对象的缓存,存放原始的 Bean 对象(属性尚未赋值),用于解决循环依赖
  • 第三级缓存 singletonFactories 用于存放单例对象工厂的缓存,存放 Bean 工厂对象,用于解决循环依赖。

Spring 的单例对象的初始化主要分为三步:
(1) createBeanlnstance:实例化,其实也就是调用对象的构造方法实例化对象
(2) populateBean:填充属性,这一步主要是多bean 的依赖属性进行填充
(3) initializeBean:调用spring xml 中的 init 方法。

解决循环依赖的整个流程

整个流程大致如下:在createBeanlnstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析—下“A的某个field 或者setter依赖了B的实例对象,同时B 的某个field或者setter依赖了A的实例对象”这种循环依赖的清况。A首先完成了初始化的第—步,并且将自己提前曝光到 singletonFactories 中,此时进行初始化的第二步,发现自己依赖对象B, 此时就尝试去get(B), 发现B还没有被create, 所以走create 流程, B在初始化第—步的时候发现自己依赖了对象A,于是尝试get(A), 尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories, 由于A通过ObjectFactory将自己提前曝光了,所以B能够通过Obj ect Facto ry.getObj ect拿到A对象(虽然A还没有初始化完全,但是总比没有好呀), B拿到A对象后顺利完成了初始化阶段1 、2 、3, 完全初始化之后将自己放入到一级缓存s i n g letonObj ects 中。此时返回A中, A此时能拿到B 的对象顺利完成自己的初始化阶段2 、3, 最终A也完成了初始化,进去了一级缓存 singletonObjects 中,而且更加幸运的是,由于B拿到了A的对象引用, 所以B现在hold住的A对象完成了初始化。

在这里插入图片描述

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值