第五章 Spring框架

简介: 第五章 Spring框架

谈谈你对Spring IOC 与 DI的理解 ?
嗯,好的 ~
● Spring的IOC,翻译过来,叫控制反转。 指的是在Spring中使用工厂模式,为我们创建了对象,并且将这些对象放在了一个容器中,我们在使用的时候,就不用每次都去new对象了,直接让容器为我们提供这些对象就可以了。 这就是控制反转的思想。
● 而DI,翻译过来,叫依赖注入。 那刚才提到,现在对象已经交给容器管理了,那程序运行时,需要用到某个对象,此时就需要让容器给我们提供,这个过程呢,称之为依赖注入。
可能继续追问的问题:
● 那如何将一个对象,讲给IOC容器管理呢?
那现在项目开发,都是基于Springboot构建的项目,所以呢,声明bean对象,我们只需要在对应的类上加上注解就可以了。 比如:
● 如果是controller层,直接在类上加上 @Controller 或 @RestController 注解。
● 如果是service层,直接在类上加上 @Service 注解。
● 如果是dao层,直接在类上加上 @Repository 注解。当然现在基本都是Mybatis 或 MybatisPlus,所以这个注解很少用了,都用的是 @Mapper 注解。
● 如果是一些其他的工具类、配置类啊,我们可以通过 @Component 、@Configuration 来声明。
● 那如何完成依赖注入操作呢 ? 依赖注入的方式比较多,我们可以使用构造函数注入 或 成员变量输入,也是使用对应的注解就可以了。常用的注解有两个:
○ @Autowired 和 @Resource 注解。 那 @Autowired 默认是根据类型注入,而 @Resource 注解默认是根据名称注入。
Spring容器中的bean是线程安全的吗?
不是线程安全的。
针对于这个问题呢,首先我们知道spring容器的bean默认是单例的。 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,也就是bean对象的业务方法,那如果在业务方法中操作了共享的成员变量,那可能就会存在线程安全问题。
而在Spring框架中并没有对单例bean进行任何多线程的封装处理,关于单例bean的线程安全和并发问题需要我们自行去搞定。但实际上,大部分情况下Spring 的bean并没有可变的状态(比如Controller、Service、Dao),所以在某种程度上说Spring的单例bean是线程安全的。
如果说存在这种情况,那就需要开发人员自行保证线程安全。要么,通过编码保证线程安全,要么,设置bean的作用域为 prototype。
Spring Bean的作用域如何设置,常见的取值有哪些 ?
Spring Bean的作用域可以通过 @Scope 注解来设置。常见的取值如下:
● singleton :这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中同一个名称的bean只有一个实例,也就是单例的 。
● prototype :这种范围,表示非单例的。也就是说每一次用到的bean都是一个新的 。
● request :同一个请求,使用的是同一个bean。会为每一个来自客户端的请求都创建一个实例,在请求完成以后, bean会失效并被垃圾回收器回收 。
● session:与request 请求范围类似,确保每个session会话范围内,是同一个实例,在session过期后, bean会随之失效 。
虽然,bean作用域可以设置这些值,但是在项目开发中,绝大部分的bean都不会添加这个 @Scope 注解,也就是说默认都是用的是单例的bean。
Spring容器的bean什么时候初始化的?
嗯~ 这个得分情况来看哈。
● 如果是单例的bean,默认是Spring容器启动的时候,就完成bean的初始化操作,那这是默认情况,我们可以通过 @Lazy 注解来延迟bean的初始化,延迟到第一次使用的时候。
● 而如果是非单例的bean(也就是prototype),则是在每次使用这个bean的时候,都会重新实例化一个新的bean。
聊一聊Bean的生命周期 ?
额,这个详细的步骤还是挺多的,我之前看过一些源码,我就挑几个主要的步骤说一下,大概流程是这样的。

首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等这些信息。
● 第一步在创建bean的时候,就是调用构造函数实例化bean。
● 第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowired都是这一步完成。
● 第三步是处理Aware接口,如果某一个bean实现了Aware接口(包括BeanNameAware、BeanFactoryAware、ApplicationContextAware)就会重写方法执行。
● 第四步是bean的后置处理器BeanPostProcessor的postProcessBeforeInitialization方法运行,这个是前置增强。
● 第五步是初始化方法执行,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct。
● 第六步是执行了bean的后置处理器BeanPostProcessor的postProcessAfterInitialization方法运行,这个是前置增强主要是对bean进行增强,有可能在这里产生代理对象。
● 到此,spring容器的bean已经创建完毕了,业务逻辑运行。
● 最后一步是销毁bean,比如自定义的方法destroy-method或@PreDestroy执行。
聊一聊Spring中bean的循环依赖问题 ?
嗯,好的,首先我来解释一下循环依赖。
循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环,比如A依赖于B,B依赖于A。
循环依赖在spring中是允许存在,spring框架通过内部的三级缓存来解决了大部分的循环依赖问题。三级缓存,每一级缓存的作用如下:
①一级缓存:singletonObjects 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:earlySingletonObjects 缓存早期的bean对象(生命周期还没走完,半成品的bean)
③三级缓存:singletonFactories 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的(比如创建代理对象)
具体的流程大概是这个样子的 :
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
第二,A在初始化的时候需要B对象,这个走B的创建的逻辑
第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
第七,二级缓存中的临时对象A清除
PS:如下代码、图示帮助理解:

@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
就比如A类中,需要依赖注入一个属性B; 而B类中,也需要依赖注入一个A属性。 那么当在实例化bean的时候就会出现如下现象:

A. 假设先获取到A的构造函数,然后通过构建函数创建A对象。
B. 然后A对象创建后,根据A的生命周期,还需要为其注入依赖的bean对象,A中依赖了B对象,所以此时会到一级缓存singletonObjects 单例池中查找B类对象的bean对象,拿来赋值给A对象的b属性。如果一级缓存(单例池)中,没有这个对象,紧接着就会去创建B对象。
C. 那么在创建B类的Bean的过程中,首先调用B类的构造函数,来创建B对象,然后接下来就要进行依赖注入,注入属性A,此时发现,一级缓存singletonObjects 单例池中并没有A类对应的B,那么此时,又会去创建类A这个bean。 此时,就出现了循环依赖。
那如何解决循环依赖呢? 要想打破上述的缓存,就需要一个中间人的参与,这个中间人就是二级缓存。

A类的Bean在创建过程中,在进行依赖注入之前,先把A的原始对象放入二级缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了)。
放入缓存后,再进行依赖注入,此时A类的Bean依赖了B类的Bean,如果B类的Bean不存在,则需要创建B类的Bean,而创建B类的Bean的过程和A类一样,也是先创建一个B类的原始对象,然后把B类的原始对象提早暴露出来放入缓存中,然后在对B类的原始对象进行依赖注入A,此时能从二级缓存中拿到A类的原始对象(虽然是A类的原始对象,还不是最终的Bean),B类的原始对象依赖注入完了之后,B类的生命周期结束,那么A类的生命周期也能结束。
因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。
这样,就解决了循环依赖的问题。
如果我们在项目中使用了AOP,基于AOP为类生成了代理对象,此时,靠上述的两级缓存就解决不了循环依赖问题了,那这个时候就需要借助于第三级缓存 singletonFactories 来解决了。

A. singletonObjects : 一级缓存,缓存经过了完整生命周期的bean
B. earlySingletonObjects : 二级缓存,缓存原始对象,也就是半成品的bean
C. singletonFactories : 三级缓存,缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。谈谈你对Spring IOC 与 DI的理解 ?
嗯,好的 ~
● Spring的IOC,翻译过来,叫控制反转。 指的是在Spring中使用工厂模式,为我们创建了对象,并且将这些对象放在了一个容器中,我们在使用的时候,就不用每次都去new对象了,直接让容器为我们提供这些对象就可以了。 这就是控制反转的思想。
● 而DI,翻译过来,叫依赖注入。 那刚才提到,现在对象已经交给容器管理了,那程序运行时,需要用到某个对象,此时就需要让容器给我们提供,这个过程呢,称之为依赖注入。
可能继续追问的问题:
● 那如何将一个对象,讲给IOC容器管理呢?
那现在项目开发,都是基于Springboot构建的项目,所以呢,声明bean对象,我们只需要在对应的类上加上注解就可以了。 比如:
● 如果是controller层,直接在类上加上 @Controller 或 @RestController 注解。
● 如果是service层,直接在类上加上 @Service 注解。
● 如果是dao层,直接在类上加上 @Repository 注解。当然现在基本都是Mybatis 或 MybatisPlus,所以这个注解很少用了,都用的是 @Mapper 注解。
● 如果是一些其他的工具类、配置类啊,我们可以通过 @Component 、@Configuration 来声明。
● 那如何完成依赖注入操作呢 ? 依赖注入的方式比较多,我们可以使用构造函数注入 或 成员变量输入,也是使用对应的注解就可以了。常用的注解有两个:
○ @Autowired 和 @Resource 注解。 那 @Autowired 默认是根据类型注入,而 @Resource 注解默认是根据名称注入。
Spring容器中的bean是线程安全的吗?
不是线程安全的。
针对于这个问题呢,首先我们知道spring容器的bean默认是单例的。 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,也就是bean对象的业务方法,那如果在业务方法中操作了共享的成员变量,那可能就会存在线程安全问题。
而在Spring框架中并没有对单例bean进行任何多线程的封装处理,关于单例bean的线程安全和并发问题需要我们自行去搞定。但实际上,大部分情况下Spring 的bean并没有可变的状态(比如Controller、Service、Dao),所以在某种程度上说Spring的单例bean是线程安全的。
如果说存在这种情况,那就需要开发人员自行保证线程安全。要么,通过编码保证线程安全,要么,设置bean的作用域为 prototype。
Spring Bean的作用域如何设置,常见的取值有哪些 ?
Spring Bean的作用域可以通过 @Scope 注解来设置。常见的取值如下:
● singleton :这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中同一个名称的bean只有一个实例,也就是单例的 。
● prototype :这种范围,表示非单例的。也就是说每一次用到的bean都是一个新的 。
● request :同一个请求,使用的是同一个bean。会为每一个来自客户端的请求都创建一个实例,在请求完成以后, bean会失效并被垃圾回收器回收 。
● session:与request 请求范围类似,确保每个session会话范围内,是同一个实例,在session过期后, bean会随之失效 。
虽然,bean作用域可以设置这些值,但是在项目开发中,绝大部分的bean都不会添加这个 @Scope 注解,也就是说默认都是用的是单例的bean。
Spring容器的bean什么时候初始化的?
嗯~ 这个得分情况来看哈。
● 如果是单例的bean,默认是Spring容器启动的时候,就完成bean的初始化操作,那这是默认情况,我们可以通过 @Lazy 注解来延迟bean的初始化,延迟到第一次使用的时候。
● 而如果是非单例的bean(也就是prototype),则是在每次使用这个bean的时候,都会重新实例化一个新的bean。
聊一聊Bean的生命周期 ?
额,这个详细的步骤还是挺多的,我之前看过一些源码,我就挑几个主要的步骤说一下,大概流程是这样的。

首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等这些信息。
● 第一步在创建bean的时候,就是调用构造函数实例化bean。
● 第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowired都是这一步完成。
● 第三步是处理Aware接口,如果某一个bean实现了Aware接口(包括BeanNameAware、BeanFactoryAware、ApplicationContextAware)就会重写方法执行。
● 第四步是bean的后置处理器BeanPostProcessor的postProcessBeforeInitialization方法运行,这个是前置增强。
● 第五步是初始化方法执行,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct。
● 第六步是执行了bean的后置处理器BeanPostProcessor的postProcessAfterInitialization方法运行,这个是前置增强主要是对bean进行增强,有可能在这里产生代理对象。
● 到此,spring容器的bean已经创建完毕了,业务逻辑运行。
● 最后一步是销毁bean,比如自定义的方法destroy-method或@PreDestroy执行。
聊一聊Spring中bean的循环依赖问题 ?
嗯,好的,首先我来解释一下循环依赖。
循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环,比如A依赖于B,B依赖于A。
循环依赖在spring中是允许存在,spring框架通过内部的三级缓存来解决了大部分的循环依赖问题。三级缓存,每一级缓存的作用如下:
①一级缓存:singletonObjects 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:earlySingletonObjects 缓存早期的bean对象(生命周期还没走完,半成品的bean)
③三级缓存:singletonFactories 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的(比如创建代理对象)
具体的流程大概是这个样子的 :
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories
第二,A在初始化的时候需要B对象,这个走B的创建的逻辑
第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories
第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键
第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects
第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects
第七,二级缓存中的临时对象A清除
PS:如下代码、图示帮助理解:

@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
就比如A类中,需要依赖注入一个属性B; 而B类中,也需要依赖注入一个A属性。 那么当在实例化bean的时候就会出现如下现象:

A. 假设先获取到A的构造函数,然后通过构建函数创建A对象。
B. 然后A对象创建后,根据A的生命周期,还需要为其注入依赖的bean对象,A中依赖了B对象,所以此时会到一级缓存singletonObjects 单例池中查找B类对象的bean对象,拿来赋值给A对象的b属性。如果一级缓存(单例池)中,没有这个对象,紧接着就会去创建B对象。
C. 那么在创建B类的Bean的过程中,首先调用B类的构造函数,来创建B对象,然后接下来就要进行依赖注入,注入属性A,此时发现,一级缓存singletonObjects 单例池中并没有A类对应的B,那么此时,又会去创建类A这个bean。 此时,就出现了循环依赖。
那如何解决循环依赖呢? 要想打破上述的缓存,就需要一个中间人的参与,这个中间人就是二级缓存。

A类的Bean在创建过程中,在进行依赖注入之前,先把A的原始对象放入二级缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了)。
放入缓存后,再进行依赖注入,此时A类的Bean依赖了B类的Bean,如果B类的Bean不存在,则需要创建B类的Bean,而创建B类的Bean的过程和A类一样,也是先创建一个B类的原始对象,然后把B类的原始对象提早暴露出来放入缓存中,然后在对B类的原始对象进行依赖注入A,此时能从二级缓存中拿到A类的原始对象(虽然是A类的原始对象,还不是最终的Bean),B类的原始对象依赖注入完了之后,B类的生命周期结束,那么A类的生命周期也能结束。
因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。
这样,就解决了循环依赖的问题。
如果我们在项目中使用了AOP,基于AOP为类生成了代理对象,此时,靠上述的两级缓存就解决不了循环依赖问题了,那这个时候就需要借助于第三级缓存 singletonFactories 来解决了。

A. singletonObjects : 一级缓存,缓存经过了完整生命周期的bean
B. earlySingletonObjects : 二级缓存,缓存原始对象,也就是半成品的bean
C. singletonFactories : 三级缓存,缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。

相关文章
|
13天前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
103 2
|
3月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
141 0
|
3月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
402 0
|
3月前
|
NoSQL Java 数据库连接
SpringBoot框架
Spring Boot 是 Spring 家族中最流行的框架,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置、起步依赖和内嵌服务器三大核心功能,大幅减少配置复杂度,提升开发效率。开发者可快速构建独立运行的 Web 应用,并支持多种数据访问技术和第三方集成。
|
4月前
|
Java API 网络架构
基于 Spring Boot 框架开发 REST API 接口实践指南
本文详解基于Spring Boot 3.x构建REST API的完整开发流程,涵盖环境搭建、领域建模、响应式编程、安全控制、容器化部署及性能优化等关键环节,助力开发者打造高效稳定的后端服务。
515 1
|
3月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
124 0
|
3月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
323 0
|
3月前
|
消息中间件 NoSQL Java
SpringBoot框架常见的starter你都用过哪些 ?
本节介绍常见的Spring Boot Starter,分为官方(如Web、AOP、Redis等)与第三方(如MyBatis、MyBatis Plus)两类,用于快速集成Web开发、数据库、消息队列等功能。
248 0
|
3月前
|
缓存 Java 数据库
第五章 Spring框架
第五章 Spring框架