Spring(一)Spring基础与SpringIOC

文章内容来源:拉勾教育Java高薪训练营(侵权联系删除)

一、spring概述

1.1 spring简介

  Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 SpringMVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已经成为使用最多的 Java EE 企业应用开源框架。
  Spring 官方网址:http://spring.io

1.2 spring优势

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

1.3 spring的核心结构

spring核心模块
  • Spring核心容器(Core Container)
    容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心容器之上。
  • 面向切面编程(AOP)/Aspects
    Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础,与DI⼀样,AOP可以帮助应用对象解耦。
  • 数据访问与集成(Data Access/Integration)
    Spring的JDBC和DAO模块封装了大量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败而引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进行了集成,如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
  • Web模块
    该模块提供了SpringMVC框架给Web应用,还提供了多种构建和其它应用交互的远程调用方案。 SpringMVC框架在Web层提升了应用的松耦合水平。
  • Test
    为了使得开发者能够很方便的进行测试,Spring提供了测试模块以致力于Spring应用的测试。 通过该模块,Spring为使用Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。

二、Spring IOC

2.1 IOC的基本概念

2.1.1 什么是IOC

  IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现。
描述的事情:Java开发领域对象的创建,管理的问题。
传统开发方式:比如类A依赖于类B,往往会在类A中new⼀个B的对象。
IOC思想下开发方式:我们不用自己去new对象了,而是由IOC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IOC容器要即可。我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不用考虑对象的创建、管理等⼀系列事情)
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利。
反转:控制权交给外部环境了(Spring框架、IOC容器)。


springIOC

2.1.2 IOC解决了什么问题

IoC解决对象之间的耦合问题。
比如 Service层需要引入Dao,如果没有IOC,我们需要手动去在service里面手动new一个Dao的实现类。
这样service层跟Dao层代码就耦合在一起了,如果Dao的逻辑发生重大变化,我们新增加了Dao的实现类,我们要把Service里面跟Dao耦合的代码都要改一遍。不符合面向接口开发的原则。
当我们将类的控制权交给Spring的时候我们只需要声明接口即可,耦合度较小。


代码解耦

2.1.3 IOC和DI的关系

DI:Dependancy Injection(依赖注入)。
怎么理解:
IOC和DI描述的是同⼀件事情,只不过角度不⼀样罢了。
IOC是站在对象的角度:对象实例化及其管理的权利交给了(反转)给了容器。
DI是站在容器的角度:容器会把对象依赖的其他对象注入(送进去),比如A对象实例化过程中因为声明了一个B类型的属性,那么就需要容器把B对象注入给A。

2.2 IOC基础

2.2.1 启动容器的方式

1.Java环境下启动IoC容器:

ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)。
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件。
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器。

2.WEB环境下启动IoC容器:

a.从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 <display-name>Archetype Created Web Application</display-name>
 <!--配置Spring ioc容器的配置⽂件-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <!--使⽤监听器启动Spring的IOC容器-->
 <listener>
 <listener�class>org.springframework.web.context.ContextLoaderListener</listener�class>
 </listener>
</web-app>
b.从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
 <display-name>Archetype Created Web Application</display-name>
 <!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
 <context-param>
 <param-name>contextClass</param-name>
 <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
 </context-param>
 <!--配置启动类的全限定类名-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>com.lagou.edu.SpringConfig</param-value>
 </context-param>
 <!--使⽤监听器启动Spring的IOC容器-->
 <listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener�class>
 </listener>
</web-app>

2.2.2 bean的实例化方式

(1) xml配置文件方式实例化

xml文件头

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 https://www.springframework.org/schema/beans/spring-beans.xsd">

a.使用无参构造函数
在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建失败。

<!--配置service对象-->
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>

b.使用静态方法创建
在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供⼀个创建对象的方法,恰好这个方法是static修饰的方法,即是此种情况。
例如,我们在做Jdbc操作时,会用到java.sql.Connection接口的实现类,如果是mysql数据库,那么用的就是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = newJDBC4Connection() ,因为我们要注册驱动,还要提供URL和凭证信息,⽤ DriverManager.getConnection 方法来获取连接。
那么在实际开发中,尤其早期的项⽬没有使用Spring框架来管理对象的创建,但是在设计时使用了工厂模式解耦,那么当接入pring之后,工厂类创建对象就具有和上述例子相同特征,即可采用此种方式配置。

<!--使用静态方法创建对象的配置方式-->
<bean id="userService" class="com.lagou.factory.BeanFactory"
 factory-method="getTransferService"></bean>

c.使用实例化方法创建
此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是类中的一个普通方法。此种方式比静态方法创建的几率要高⼀些。在早期开发的项目中,工厂类中的方法有可能是静态的,也有可能是非静态方法,当是非静态方法时,即可采用下面的配置方式。

<!--使⽤实例方法创建对象的配置方式-->
<bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>

(2) xml和注解模式下bean的实例化

1)实际企业开发中,纯xml模式使用已经很少了。
2)引入注解功能,不需要引入额外的jar。
3)xml+注解结合模式,xml文件依然存在,所以spring IOC容器的启动仍然从加载xml开始。
4)哪些bean的定义写在xml中,哪些bean的定义使用注解。
第三方jar中的bean定义在xml,比如德鲁伊数据库连接池。
自己开发的bean定义使用注解,比如@service等。

a.xml中标签与注解的对应(IoC)

xml形式 对应注解形式
标签 @Component("accountDao"),注解加在类上bean的id属性内容直接配置在注解后面如果不配置,默认定义个这个bean的id为类的类名首字母小写;另外,对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的用法完全⼀样,只是为了更清晰的区分而已
标签的scope属性 @Scope("prototype"),默认单例,注解加在类上
标签的init-method属性 @PostConstruct,注解加在方法上,该方法就是初始化后调用的方法
标签的destory-method属性 @Scope("prototype"),默认单例,注解加在类上
标签的scope属性 @PreDestory,注解加在方法上,该方法就是销毁前调⽤的方法

b.DI 依赖注入的注解实现方式

@Autowired(推荐使用)
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired。
@Autowired采取的策略为按照类型注入。
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注入进来。这样会产生⼀个问题,当⼀个类型有多个bean值的时候,会造成无法选择具体注入哪⼀个的情况,这个时候我们需要配合着@Qualifier使用。@Qualifier告诉Spring具体去装配哪个对象。

public class TransferServiceImpl {
 @Autowired
 @Qualifier(name="jdbcAccountDaoImpl") 
 private AccountDao accountDao; }

@Resource
@Resource 注解由 J2EE 提供,需要导入包 javax.annotation.Resource。
@Resource 默认按照 ByName 自动注入。

public class TransferService {
 @Resource 
 private AccountDao accountDao;
 @Resource(name="studentDao") 
 private StudentDao studentDao;
 @Resource(type="TeacherDao") 
 private TeacherDao teacherDao;
 @Resource(name="manDao",type="ManDao") 
 private ManDao manDao;
}
  • 如果同时指定了 name 和 type,则从Spring上下文中找到唯⼀匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了 name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了 type,则从上下文中找到类似匹配的唯⼀bean进行装配,找不到或是找到多个,都会抛出异常。
  • 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;
    @Resource 在 Jdk 11中已经移除,如果要使用,需要单独引入jar包

(3) 纯注解模式下bean的实例化

改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动类加上对应注解。

  • @Configuration 注解,表名当前类是⼀个配置类。
  • @ComponentScan 注解,替代 context:component-scan。
  • @PropertySource,引入外部属性配置文件。
  • @Import 引入其他配置类。
  • @Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息。
  • @Bean 将方法返回对象加入SpringIOC 容器。

2.2.3 不同作用范围的Bean生命周期

bean的生命周期:实例化——属性赋值——初始化——销毁

单例模式:singleton

对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象生命周期与容器相同。

多例模式:prototype

对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就⼀直活着。
对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

2.3 IOC高级特性

2.3.1 lazy-Init 延迟加载

Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。
提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singletonbean。
比如:

<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为: <bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init="false",立即加载,表示在spring启动时,立刻进行实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean
设置为延迟实例化。

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />

设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第⼀次向容器通过getBean 索取 bean 时实例化的。如果⼀个设置了立即加载的 bean1,引用了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调用时才被实例化的规则。也可以在容器层次中通过在 元素上使用 "default-lazy-init" 属性来控制延时初始化。
如下面配置:

<beans default-lazy-init="true">
 <!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope="pototype" (多例)时,即使设置了 lazy-init="false",容器启动时也不会实例化bean,而是调用 getBean 方法实例化的。
应用场景
(1)开启延迟加载⼀定程度提高容器启动和运转性能。
(2)对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从⼀开始该 Bean 就占用资源。

2.3.2 FactoryBean 和 BeanFactory

BeanFactory

BeanFactory是容器的顶级接口,定义了容器的⼀些基础行为,负责生产和管理Bean的⼀个工厂,具体使用它下面的子接口类型,比如ApplicationContext;

FactoryBean

Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean),FactoryBean可以生成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义Bean的创建过程。
Bean创建的三种方式中的静态方法和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架⼀些组件中会使用,还有其他框架和Spring框架整合时使用。
下面我们可以通过FactoryBean实现自定义Bean的创建过程,完成复杂bean的创建。
如图:Spring预置的FactoryBean中定义了如下方法:

import org.springframework.lang.Nullable;

// 可以让我们自定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
 @Nullable
 // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
 T getObject() throws Exception;
 @Nullable
 // 返回FactoryBean创建的Bean类型
 Class<?> getObjectType();
 // 返回作⽤域是否单例
 default boolean isSingleton() {
   return true;
 } 
}

Company类:

package com.lagou.edu.pojo;

public class Company {

    private String name;

    private String address;

    private int scale;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getScale() {
        return scale;
    }

    public void setScale(int scale) {
        this.scale = scale;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", scale=" + scale +
                '}';
    }
}

CompanyFactoryBean

package com.lagou.edu.factory;


import com.lagou.edu.pojo.Company;
import org.springframework.beans.factory.FactoryBean;

public class CompanyFactoryBean implements FactoryBean<Company>{

    private String companyInfo; // 公司名称,地址,规模
    
    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }
    @Override
    public Company getObject() throws Exception {
        // 模拟创建复杂对象Company
        Company company = new Company();
        String[] strings = companyInfo.split(",");
        company.setName(strings[0]);
        company.setAddress(strings[1]);
        company.setScale(Integer.parseInt(strings[2]));
        return company;
    }
    @Override
    public Class<?> getObjectType() {
        return Company.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

xml 配置

 <!--测试自定义FactoryBean-->
    <bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
        <property name="companyInfo" value="拉勾,中关村,500"/>
    </bean>

测试,获取FactoryBean产生的对象

Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}

测试,获取FactoryBean,需要在id之前添加“&”

Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09

2.4 IOC循环依赖

Spring框架通过其自身的三级缓存机制解决了循环依赖的问题。对于Spring中的循环依赖主要有两种情况:

1 构造器注入导致的循环依赖

Spring不能解决构造器注入的循环依赖,因为在构造器注入时,Spring容器需要先创建一个Bean A的对象,而在构造A时就需要注入另一个Bean B的对象,但此时B尚未创建或者正在创建中,这就构成了死循环。因此,为了避免构造器循环依赖,建议使用 setter 或 field 注入来代替构造器注入。

2 Setter/Field注入导致的循环依赖

Spring容器可以解决基于Setter或Field注入的循环依赖问题。这是因为Spring容器在初始化Bean的过程中采用了三级缓存策略来管理单例Bean的创建状态:
一级缓存(singletonObjects):存放完全初始化好的单例Bean。
二级缓存(earlySingletonObjects):存放提前曝光(early exposed)的单例Bean,这些Bean还未完成所有的初始化,但对象已经被实例化。
三级缓存(singletonFactories):存放Bean的工厂对象,可以用来生产未完成初始化的Bean。
当Spring容器创建Bean时,首先会尝试从一级缓存中获取,如果获取不到,则尝试创建新的Bean实例。在创建Bean的过程中,会先将半成品对象(仅实例化但未完成依赖注入的对象)放入二级缓存中,接着处理其他依赖项。如果有循环依赖发生,就可以从二级缓存中获取到彼此的引用,待所有依赖注入完成后,再将Bean从二级缓存移到一级缓存中。

下面举两个例子:

2.1 普通类循环依赖:

不涉及工厂方法创建对象的依赖,这种情况使用二级缓存就可以解决
假设有一个Spring应用中有两个单例Bean ServiceA 和 ServiceB,它们之间存在循环依赖:
ServiceA 类中有一个成员变量 serviceB,表示它依赖于 ServiceB。
ServiceB 类中也有一个成员变量 serviceA,表示它依赖于 ServiceA。

@Service
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private ServiceA serviceA;

    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

Spring是如何使用三级缓存解决这样的循环依赖呢?

1.创建ServiceA:
  • 当Spring容器开始创建 ServiceA 时,首先尝试从一级缓存(singletonObjects)中获取,未能找到。
  • 然后开始实例化 ServiceA,在实例化过程中发现需要注入 ServiceB,于是转向创建 ServiceB。
2.创建ServiceB:
  • 同样地,Spring容器在一级缓存中找不到 ServiceB,开始实例化 ServiceB。
  • 在实例化 ServiceB 并准备注入 ServiceA 时,Spring发现 ServiceA 正在创建中,于是从二级缓存(earlySingletonObjects)查找。
  • 此时 ServiceA 已经被实例化但尚未注入属性,所以Spring从二级缓存中找到了 ServiceA 的半成品对象,并注入到 ServiceB 中。
3.回溯到ServiceA:
  • 完成 ServiceB 的实例化和属性注入后,将 ServiceB 的半成品对象放入二级缓存。
  • 回到 ServiceA 的创建流程,此时 ServiceB 已经在二级缓存中,Spring可以从二级缓存中取出完整的 ServiceB 对象注入到 ServiceA 中。
  • 最终,当 ServiceA 的所有依赖都完成注入后,将完整的 ServiceA 对象从二级缓存移动到一级缓存,同时将 ServiceB 也从二级缓存移动到一级缓存。

这样一来,即使存在循环依赖,Spring也能通过三级缓存机制保证依赖关系的正确建立,且不会陷入无限递归的创建过程中。注意,以上描述的是setter注入的情况,构造器注入的循环依赖Spring是无法解决的.

2.2 工厂方法类循环依赖:

在工厂方法模式下,Spring的确可以利用三级缓存来解决循环依赖问题。下面是一个简化的例子来展示这一过程:

假设我们有两个互相依赖的Bean:ComponentA 和 ComponentB,并且通过工厂方法来创建它们

@Service
public class ComponentA {

    private final ObjectFactory<ComponentB> componentBFactory;

    @Autowired
    public ComponentA(ObjectFactory<ComponentB> componentBFactory) {
        this.componentBFactory = componentBFactory;
    }

    public void doSomething() {
        ComponentB componentB = componentBFactory.getObject();
        // ...
    }
}

@Service
public class ComponentB {

    private final ComponentA componentA;

    @Autowired
    public ComponentB(ComponentA componentA) {
        this.componentA = componentA;
    }
}

现在我们来看Spring如何处理循环依赖:

1.创建ComponentA:
  • Spring试图从一级缓存中获取 ComponentA,未找到。
  • 开始创建 ComponentA,注意到构造器参数是 ObjectFactory<ComponentB>,开始创建 ComponentB。
2.创建ComponentB:
  • 在创建 ComponentB 时,发现需要注入 ComponentA,于是从一级缓存查找,未找到。
  • 开始创建 ComponentA,但它已经处于创建过程中,于是Spring开始使用三级缓存。
3。使用三级缓存:
  • Spring将 ComponentA 的工厂对象(此处是 ObjectFactory<ComponentB> 实例)放入三级缓存中。
  • 然后返回到 ComponentB 的创建流程,从三级缓存中获取到 ComponentA 的工厂对象,注入到ComponentB 中。
4.继续创建ComponentA:
  • 完成 ComponentB 的创建后,将 ComponentB 的工厂对象(ObjectFactory<ComponentB>)放入二级缓存。
  • 回到 ComponentA 的创建流程,由于 ComponentB 的工厂对象已经存在,可以从二级缓存中获取并注入到 ComponentA。
5.最终处理:
  • 当 ComponentA 的构造器注入完成之后,将其完整实例放入一级缓存,并将 ComponentB 也从二级缓存移动到一级缓存。

通过三级缓存,Spring能够在创建 ComponentA 的过程中保存一个能够生成 ComponentB 实例的工厂对象,这样即使在创建过程中存在循环依赖,也可以暂时绕过,等到真正需要 ComponentB 实例时再通过工厂对象去创建。这样既保持了依赖关系的正确性,也避免了循环引用带来的初始化问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容