【Java八股-第十五期】Bean - Spring
提纲:
🔥Bean
创建过程
循环依赖
配置方法
作用域
线程安全
🎈面试八股真题
1、Autowired和Resource关键字的区别?
2、依赖注入的方式有几种,各是什么?
3、解释一下spring bean的生命周期
4、解释Spring支持的几种bean的作用域?
5、Spring基于xml注入bean的几种方式?
6、Spring框架中都用到了哪些设计模式?
7、说说Spring 中 ApplicationContext 和 BeanFactory 的区别
8、Spring 框架中的单例 Bean 是线程安全的么?
9、Spring 是怎么解决循环依赖的?
10、说说事务的隔离级别
11、说说事务的传播级别
12、Spring 事务实现方式
13、Spring框架的事务管理有哪些优点
14、事务三要素是什么?
15、事务注解的本质是什么?
一、Bean
1. 创建过程
-
getBean() 查看缓存
-
1、确认 BeanName,查看缓存中是否存在 Bean 对象,若存在直接返回
-
2、查看父工厂是否存在
-
-
createBean() 实例化
-
1、加载 BeanDefinition,将其转化为 RootBeanDefinition
-
2、实例化 Bean,调用 Bean 的构造方法进行实例化,若 Bean 是单例,支持循环依赖且正在创建,将一个 SingletonFactory 回调接口放入三级缓存,方法参数为 Bean,BeanName 以及 BeanDefinition,用于暴***ean 的引用和 Bean 的提前 AOP,解决循环依赖问题
-
-
populateBean() 依赖注入
-
1、通过 AutowireAnnotation 注解扫描 @Autowired/@Value/@Lazy/@Optional 等注解,CommonAnnotation扫描 @Resource 注解进行后置处理器装配
-
2、通过 xml 文件配置的 setter/构造 方法进行注入
-
-
initializeBean() 初始化 Bean
-
1、若 Bean 实现了 Aware 接口,如 BeanNameAware、BeanClassLoaderAware 等,通过 ApplicationAware后置处理器进行调用
-
2、若 Bean 有 @PostConstruct 注解标注的方法,使用 CommonAnnotation 后置处理器进行调用
-
3、调用 Bean 实现的 InitializingBean 接口的 affterPropertiesSet 方法
-
4、调用 xml 文件中配置的 init-method 方法
-
5、通过 AspectJ***Creator 后置处理器,进行 Bean 的 AOP 增强,生成代理对象并放入单例池
-
2.循环依赖
-
Spring 无法自行解决的循环依赖
-
1、Prototype 作用域的 Bean 的循环依赖:每一次获取 Bean 都会创建新的 Bean 对象,不会检查任何缓存
-
2、通过构造方法注入的 Bean 的循环依赖:循环依赖发生在实例化阶段,无法提前暴露对象的引用来解决
-
-
Spring 只可以解决单例 Bean 在依赖注入阶段发生的循环依赖
-
三级缓存
-
1、SingletonObject:一级缓存,存放已经创建好的 Bean,ConcurrentHashMap
-
2、EarlySingletonObject:二级缓存,存放正在创建的已经实现 AOP 的 Bean 对象,HashMap
-
3、SingletonFactory:三级缓存,存放 SingletonFactory,本质上是存放一个回调接口,用于提前暴***ean 的引用和实现 Bean 的提前 AOP
-
-
-
原理:假设 A—B 循环依赖
-
1、实例化 A 对象,将 A 对象的引用、A 的 BeanName,A 的 BeanDefinition 作为参数传给回调接口,即 SingletonFactory 接口的getEarlyBeanReference() 方法,并将其作为 Value 存入三级缓存
-
2、依赖注入 B 对象,递归创建 B 对象
-
3、B 对象的依赖注入阶段,依赖注入了 A,此时先去二级缓存中查看是否存在 A 的提前引用,若存在直接取出使用,若不存在,从三级缓存中取出 A 的 SingletonFactory,调用 SingletonFactory 的 getObject 方法,对 A 进行提前 AOP,获取 A 的早期代理对象,并放入二级缓存
-
-
问题
-
1、第三级缓存:第三级缓存是解决循环依赖的核心,它通过提前暴露 A 在创建时的引用,来解决循环依赖,但不能简单的存放实例化得到的 A 的普通对象引用,假设存放的是 A 的普通对象,那 B 在依赖注入时获得的就是没有经过 AOP 增强的 A 对象,因此需要存放一个 SingletonFacotry 回调接口,并传入创建 A 的 BeanName、Bean、BeanDefinition 信息,在 B 依赖注入 A 时,就可以调用回调接口的 getObject 方法,对 A 进行提前 AOP,获取 A 的早期代理对象
-
2、第二级缓存:第二级缓存用于存放 A 的创建中对象的引用,假设 A-B循环依赖,A-C 也循环依赖,若没有第二级缓存,C 在注入 A 时,也会从三级缓存中取出 SingletonFactory 进行 AOP,生成代理对象,这样就破坏了 A 的单例性,因此在实际源码中,会采用 Double check 的方式,对二级缓存中是否存在 A 对象进行判断,并在 Synchronize 同步代码块中,调用 SingletonFactory 的 getObject 方法,调用完后放入二级缓存,并删除三级缓存中的 SingletonFactory,因此,二级缓存的 put 方法和三级缓存的 remove 方法都是绑定出现的,使用 ConcurrentHashMap 无法保证多个操作组合的原子性,只能加锁,所以二级缓存与三级缓存使用的是 HashMap 而不是 ConcurrentHashMap
-
3.配置方法
-
xml 文件配置
-
1、set 方法,在 xml 文件中配置 setter 方法的 ref 参数为一个 BeanName 来注入
-
2、构造方法,和 set 方法类似
-
3、接口,代码侵入大,不利于理解,麻烦,不用
-
-
@Autowired 注解与 @Resource 注解
-
1、前者是 Spring 的注解,由 AutowireAnnotation 后置处理器扫描,后者是 Java 本身的注解,由 CommonAnnotation 后置处理器扫描
-
2、前者默认按类型(ByType)进行装配,并默认 Bean 必须存在,后者默认按 BeanName (ByName)进行装配
-
-
@Component 及衍生的语义注解
4.作用域
-
singleton:单例,Spring 的单例指的是 BeanName 的单例,而不是 Bean 的类型的单例
-
prototype:多例,每一次获取创建一个 Bean
-
request:在 request 域内单例,即一个 request 使用一个实例,生命周期随 request
-
session:类似 request,按 session 会话域算
-
golobal-session:所有会话共享一个实例
5.线程安全
-
prototype:线程安全的,每一个线程都有自己的 Bean
-
无状态 singleton:线程安全,不存储状态,没有可以改变的共享变量
-
有状态 singleton:最好不要定义有状态 Bean 的 singleton,实在要用,可以用分布式锁的同步解决方式,也可以用ThreadLocal 的异步方式
二、面试八股真题🎈🎈🎈
1、Autowired和Resource关键字的区别?
-
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
-
1、共同点
-
两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
-
-
2、不同点
-
(1)@Autowired
-
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
-
-
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:
-
-
-
(2)@Resource
-
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。
-
@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
-
-
注:最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
-
@Resource装配顺序:
-
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
-
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
-
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
-
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
-
-
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
-
-
2、依赖注入的方式有几种,各是什么?
-
一、构造器注入
-
将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
-
优点:
-
对象初始化完成后便可获得可使用的对象。
-
-
缺点:
-
当需要注入的对象很多时,构造器参数列表将会很长; 不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,麻烦。
-
-
-
二、setter方法注入
-
IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。
-
优点:
-
灵活。可以选择性地注入需要的对象。
-
-
缺点:
-
依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。
-
-
-
三、接口注入
-
依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。
-
优点 :
-
接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
-
-
缺点:
-
侵入性太强,不建议使用。
-
-
PS:什么是侵入性? 如果类A要使用别人提供的一个功能,若为了使用这功能,需要在自己的类中增加额外的代码,这就是侵入性。
-
3、解释一下spring bean的生命周期
-
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
-
Spring上下文中的Bean生命周期也类似,如下:
-
(1)实例化Bean:
-
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
-
-
(2)设置对象属性(依赖注入):
-
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
-
-
(3)处理Aware接口:
-
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的x
-
-
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【📫专栏目录在最底部📫】 - 本专栏适合于JAVA已经入门的学生或人士,有一定的编程基础。 - 本专栏特点: 本专刊囊括了JAVA、Spring、计算机网路、操作系统、计算机网络、MySQL、算法与数据结构、中间件等一系列知识点,总结出了高频面试考点(附有答案),事半功倍,为大家春秋招助力。 - 本专栏内容分为五章