Java面试题冲刺第十八天--Spring框架3
面试题1:bean 的加载过程是怎样的?
我们知道, spring 的工作流主要包括以下两个环节:
- 解析,读 xml 配置,扫描类文件,从配置或者注解中获取 bean 的定义信息,注册一些扩展功能。
- 加载,通过解析完的定义信息获取 bean 实例。
下面是跟踪了 getbean的调用链创建的流程图,为了能够很好地理解 bean 加载流程,省略一些异常、日志和分支处理和一些特殊条件的判断。
从上面的流程图中,可以看到一个 bean 加载主要会经历这么几个阶段(标绿内容):
- 获取 beanname,对传入的 name 进行解析,转化为可以从 map 中获取到 beandefinition 的 bean name。
- 合并 bean 定义,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 bean 定义信息。
- 实例化,使用构造或者工厂方法创建 bean 实例。
- 属性填充,寻找并且注入依赖,依赖的 bean 还会递归调用 getbean 方法获取。
- 初始化,调用自定义的初始化方法。
- 获取最终的 bean,如果是 factorybean 需要调用 getobject 方法,如果需要类型转换调用 typeconverter 进行转化。
以上便是spring对bean解析注册的全过程,总结一下大致步骤:
- 加载xml文件,封装成resource对象;
- 调用reader对象方法读取xml文件内容,并将相关属性放到beandefinition实例;
- 将beandefinition对象放到beanfactory对象,用于调用;
追问1:什么是循环依赖?
举个例子,这里有三个类 a、b、c,然后 a 关联 b,b 关联 c,c 又关联 a,这就形成了一个循环依赖。如果是方法调用是不算循环依赖的,循环依赖必须要持有引用。
循环依赖发生的场景:
- 构造器循环依赖:依赖的对象是通过构造器传入的,发生在实例化 bean 的时候。
- 设值循环依赖:依赖的对象是通过 setter 方法传入的,对象已经实例化,发生属性填充和依赖注入的时候。
- 如果是构造器循环依赖,本质上是无法解决的。比如我们准调用 a 的构造器,发现依赖 b,于是去调用 b 的构造器进行实例化,发现又依赖 c,于是调用 c 的构造器去初始化,结果依赖 a,整个形成一个死结,导致 a 无法创建。
- 如果是设值循环依赖,spring 框架只支持单例下的设值循环依赖。spring 通过对还在创建过程中的单例,缓存并提前暴露该单例,使得其他实例可以引用该依赖。
追问2:循环依赖得解决思路是什么样的?
spring解决循环依赖,主要的思路就是依据三级缓存(解链)。
在实例化a时调用dogetbean,发现a依赖的b的实例,此时调用dogetbean去实例b,实例化的b的时候发现又依赖a,如果不解决这个循环依赖的话此时的dogetbean将会无限循环下去,导致内存溢出,程序奔溃。
如果spring引用一个早期对象,并且把这个"早期引用"并将其注入到容器中,让b先完成实例化,此时a就获取b的引用,完成实例化。
一级缓存:singletonobjects,存放完全实例化属性赋值完成的bean,直接可以使用。
二级缓存:earlysingletonobjects,存放早期bean的引用,尚未属性装配的bean
三级缓存:singletonfactories,三级缓存,存放实例化完成的bean工厂。
面试题2:@resource和@autowired有什么区别?
- @autowired 根据类型注入
- @resource 默认根据名字注入,其次按照类型搜索
- @autowired @qualifie("userservice") 两个结合起来可以根据名字和类型注入,等同于@resource
1.@autowired与@resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2.@autowired默认按类型装配(bytype),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@autowired(required=false) ,如果我们想使用名称装配可以结合@qualifier注解进行使用(@autowired () @qualifier ( "xxx" )功能同@resource),如下:
@autowired @qualifier ( "userdao" ) private userdao userdao;
3.@resource默认按照名称进行装配(byname),名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。如果name属性一旦指定,就只会按照名称进行装配。
@resource (name= "basedao" ) private basedao basedao;
总结如下:
- @autowired默认按bytype自动装配,而@resource默认byname自动装配。
- @autowired只包含一个参数:required,表示是否开启自动注入,默认是true。而@resource包含七个参数,其中最重要的两个参数是:name 和 type。
- @autowired如果要使用byname,需要使用@qualifier一起配合。而@resource如果指定了name,则用byname自动装配,如果指定了type,则用bytype自动装配。
- @autowired能够用在:构造器、方法、参数、成员变量和注解上,而@resource能用在:类、成员变量和方法上。
- @autowired是spring定义的注解,而@resource是jsr-250定义的注解。
面试题3:spring 的事务传播行为有哪些,都有什么作用?
简单来讲,就是当系统中存在两个事务方法时(我们暂称为方法a和方法b),如果方法b在方法a中被调用,那么将采用什么样的事务形式,就叫做事务的传播特性
比如,a方法调用了b方法(b方法必须使用事务注解),那么b事务可以是一个在a中嵌套的事务,或者b事务不使用事务,又或是使用与a事务相同的事务,这些均可以通过指定事务传播特性来实现。
传播行为 | 意义 |
---|---|
propagation.required | 表示当前方法必须运行在事务中 。如果当前事务存在,方法将会在该事务中运行。否则会启动一个新的事务 |
propagation.supports | 表示当前方法不需要事务上下文 ,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
propagation.mandatory | 表示该方法必须在事务中运行 ,如果当前事务不存在,则会抛出一个异常 |
propagation.required_new | 表示当前方法必须运行在它自己的事务中 。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用jtatransactionmanager的话,则需要访问transactionmanager |
propagation.not_supported | 表示该方法不应该运行在事务中 。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用jtatransactionmanager的话,则需要访问transactionmanager |
propagation.never | 表示当前方法不应该运行在事务上下文中 。如果当前正有一个事务在运行,则会抛出异常 |
propagation.nested | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行 。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与propagation.required一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
总结
本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注的更多内容!