spring为什么使用三级缓存而不是两级

首先先回答几个疑问:

1.实际上代理类是相当于持有一个原对象(spring用的两种代理,Proxy和cglib都是一样):先创建对象,再创建代理类,再初始化原对象,和初始化之后再创建代理类,是一样的。

基于上述类写个main方法测试:

先根据空对象创建代理类,再初始化空对象,执行代理类方法,没问题!(忽略get/set方法)

2.尾部对象依赖前面对象,所以在尾部对象初始化时,就调用三级缓存中对象工厂的接口方法,即AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法根据前面的空对象创建代理类,并设值给尾部对象。

前面的对象在装配和初始化完成之后,spring通过这段代码,将二级缓存中的代理类取出返回,最后会设置到一级缓存中,从而保证尾部对象依赖的,和容器中的前面对象,是一个对象。


下面说下我的结论:只用两级缓存可以解决循环依赖,甚至一级缓存就行(AOP也同样适用)。

要理解下面的内容需要阅读过一定的spring源码,基础较弱的读者建议去看本回答的出处: 【超级干货】为什么spring一定要弄个三级缓存?。以免错过此次刷新你认知的机会(不是吹牛)。

不信你可以改下添加三级缓存的源码,直接加入第二层缓存或者第一层缓存里。

启动正常,所以验证了上述结论

为什么呢?很简单,解决循环依赖只需要保证创建完成的bean和创建中与设置到其他相互引用的bean里的bean是同一个就行。

没有代理的情况下(getEarlyBeanReference返回原对象)去创建AServiceImpl(简称AS):

  1. 反射创建AS的实例,并放入第一层缓存
  2. 初始化AS实例时发现需要依赖注入BS,则获取BS的实例
  3. 反射创建BS的实例,并放入第一层缓存
  4. 初始化BS实例时发现需要依赖注入AS,则获取AS的实例,直接从第一层里获取
  5. BS实例初始化完成,放入第一层缓存(此时BS里的AS只是刚创建完,未初始化)
  6. 回到第2步,AS实例初始化完成,放入第一层缓存(由于是同一个对象引用,所以BS里的AS也初始化完成)

有代理的情况下(getEarlyBeanReference返回代理对象)去创建AServiceImpl(给AServiceImpl和BServiceImpl都加上事务注解):

前面5步都没啥问题,只是第一层缓存里的是AS代理类(BS里的也是),第6步中,原AS对象初始化完成,则AS代理类其实也初始化完成,所以进行引用覆盖,返回缓存中的代理类即可。


从技术的角度看一层缓存就能解决循环依赖,为什么spring要整的这么复杂呢?

请大家换个角度,不要再站在spring使用者的角度去思考了,现在要假设自己是spring的开发者!

不要在别人已经既定设计好的方案里去猜人家当时为什么这么设计,这就是从结果反推过程,说实话很容易潜意识认为就应该这样设计,从而变成想方设法圆他人所说。

先抛却掉这几级缓存,重新审视下创建和初始化bean实例的代码。

一个bean在创建过程中可能会产生两个对象:

  • 一个是循环依赖时需要设值给与此bean相互引用的其他bean的对象(getEarlyBeanReference)
  • 一个是初始化后的对象(initializeBean)

如果现在要对bean做增强,比如实现切面,则需要生成代理类,所以spring在上述两个方法中通过BeanPostProcessor类提供了拓展点。

假如我是spring这块代码的开发者,如果我这么设计(只用一层缓存):

假设spring没有提供AOP,需要使用spring的人自己去实现,那我就需要写个说明文档告诉他们:

  1. 请实现BeanPostProcessor接口的两个方法:getEarlyBeanReference和postProcessAfterInitialization(假如我是设计者,按我的设计那我肯定都整合在一个接口里了,不会再整个SmartInstantiationAwareBeanPostProcessor)
  2. 这两个方法在bean刚创建完成但还未初始化时,和已装配并执行初始化方法之后会被调用,方法的入参bean分别是空对象和已初始化后的对象
  3. 这两个方法的返回最好是同一个对象,如果不一样,由于最后引用会重新赋值,以getEarlyBeanReference方法返回为最终值

相信这样的文档会很让使用者很困惑:

  1. 这两个方法都会执行,而且第一个方法的返回值为优先,所以我实现第二个方法干嘛呢?
  2. 第一个方法的入参还是个空对象,没有什么有用的信息啊?
  3. 第二个方法的入参里有有用的信息,但是返回的对象还是会被第一个方法的覆盖啊?
而且还暴露了很多内部设计细节,一个优秀的框架就是要让使用者对内部细节知道的越少越好,这样才便于迭代升级。

所以大家有没有发现,这种设计,让getEarlyBeanReference成为了创建bean时必会被调用的核心方法,而原本此处只是为了循环依赖时先给其他bean赋值!

倘若我们压根都没有循环依赖,bean本身的创建流程就应该是先new(一般是通过反射创建)一个,再装配初始化,最后放入实例缓存,此种设计就成了本末倒置!

那怎么调整呢?让getEarlyBeanReference延迟触发,只在有循环依赖时被引用的bean需要赋值当前bean时才触发!

所以就有了对象工厂ObjectFactory,也就有了第三级缓存

最理想的情况下,可以不用第二级缓存

举个例子,A依赖B,B依赖A和C和D,C和D又依赖A,创建A的时候初始化需要B,创建B的时候初始化需要A,拿到A的ObjectFactory后调用接口方法获取对象,B还需要C和D,它们又需要去调用A的ObjectFactory,所以就重复调用了getObject方法,其实只要getEarlyBeanReference方法实现保证同一个beanName返回同一个对象,就不需要第二级缓存。

但是,这又暴露了内部实现细节,假如我弄个BeanPostProcessor实现类,spring也没有提示我要遵守上述约定,而我在getEarlyBeanReference方法里只是创建新对象返回,这就会导致B里面的是A1,C里面是A2,D里面是A3,那完了,芭比Q了!

所以让实现者去做重复性判断是不可控的,很容易出现问题,于是乎引入了第二级缓存,当调用三级缓存里的对象工厂的getObject方法之后,spring就会把返回值放入二级缓存,删除三级缓存,这样C和D取的就是二级缓存里的A对象,和B里的是同一个。

所以二级缓存和三级缓存其实是一套组合拳,不要拆成两个独立的东西去理解,出发点就不对。

基于这种设计,没有发生循环依赖的bean就是正常的创建流程,有相互引用的bean(除链尾的那个,比如之前的BS)会触发getEarlyBeanReference。

spring其实也不想你用框架前还要先了解循环引用,所以把getEarlyBeanReference方法设计在了SmartInstantiationAwareBeanPostProcessor接口中。从设计者这里的注释也能看出:此接口是一个专用接口,主要用于框架内的内部使用!


AOP是spring内部集成的,它的开发者知道这些逻辑,所以AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口,可以看看其对这两个方法的实现。

循环引用的bean的创建过程会触发这两个方法,所以在getEarlyBeanReference方法中会打标记再去判断是否需要创建代理类(wrapIfNecessary),而postProcessAfterInitialization方法则需要先判断标记,以免重复执行wrapIfNecessary。

也得亏根据空对象去创建代理类后再去初始化原对象,和根据已初始化后的对象创建代理类效果一样。
否则这里 getEarlyBeanReference方法就没啥用了,只能返回原对象,在postProcessAfterInitialization方法返回前加上把相互引用的其他bean的引用指向方法返回值的操作了,那就没现在这么简单了。

最后再对这三级缓存做个简单的总结:

  • 第一层缓存:最基础的缓存,创建完并初始化(createBean)后的bean实例会放入,项目启动完成后获取bean实例时从此获取
  • 第三层缓存:创建bean过程中用于处理循环依赖的临时缓存,由于只有在初始化时才知道有没有循环依赖,所以通过ObjectFactory临时“存储”刚创建完的bean,并延迟触发循环依赖时被引用的bean需要赋值当前bean时去获取当前bean的逻辑,且获取对象会作为当前bean的最终对象
  • 第二级缓存:创建bean过程中用于处理循环依赖的临时缓存,搭配第三层缓存,用于其ObjectFactory返回对象的缓存,保证多个关联对象对当前bean的引用为同一个
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spring 的优点? 1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能 5.容器提供了众多的辅助类,能加快应用的开发 6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等 7.spring属于低侵入式设计,代码的污染极低 8.独立于各种应用服务器 9.spring的DI机制降低了业务对象替换的复杂性 10.Spring的高度开放性,并不强制应用完全依赖于Spring开发者可以自由选择spring的部分或全部 什么是DI机制? 依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。 spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。 设置注入的优点:直观,自然 构造注入的优点:可以在构造器中决定依赖关系的顺序。 什么是AOP? 面向切面编程(AOP)完善spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面 1.面向切面编程提供声明式事务管理 2.spring支持用户自定义的切面 面向切面编程(aop)是对面向对象编程(oop)的补充, 面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。 AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象, 是对应用执行过程中的步骤进行抽象,,从而获得步骤之间的逻辑划分。 aop框架具有的两个特征: 1.各个步骤之间的良好隔离性 2.源代码无关性 Hibernate工作原理及为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory 为什么要用: 1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。 2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作 3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。 4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。 2. Hibernate是如何延迟加载? 1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection) 2. Hibernate3 提供了属性的延迟加载功能 当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 类与类之间的关系主要体现在表与表之间的关系进行操作,它们都市对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、 4. 说下Hibernate的缓存机制 1. 内部缓存存在Hibernate中又叫一级缓存,属于应用事物级缓存 2. 二级缓存: a) 应用及缓存 b) 分布式缓存 条件:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非 关键数据 c) 第三方缓存的实现 5. Hibernate的查询方式 Sql、Criteria,object comptosition Hql: 1、 属性查询 2、 参数查询、命名参数查询 3、 关联查询 4、 分页查询 5、 统计函数 6. 如何优化Hibernate? 1.使用双向一对多关联,不使用单向一对多 2.灵活使用单向一对多关联 3.不用一对一,用多对一取代 4.配置对象缓存,不使用集合缓存 5.一对多集合使用Bag,多对多集合使用Set 6. 继承类使用显式多态 7. 表字段要少,表关联不要
面试高级开发的期间整理的面试题目,记录我面试遇到过的spring题目以及答案 目录 spring ThreadLocal的底层对象; 为什么@Service和@Repository放到实现类上面而不是接口类上面; spring 三种注入(就是从spring容器中将bean放入对象属性值中) Spring下描述依赖关系@Resource, @Autowired和@Inject的区别与联系 Spring中BeanFactory和ApplicationContext的区别 谈谈Spring IOC的理解,原理与实现? bean的生命周期,详细看上面 SpringBoot自动装配的过程的原理: spring缓存spring是如何解决的循环依赖; BeanFactory和FactoryBean有什么区别; Spring中用到的设计模式; SPI 机制(Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制), 很多地方有用到: AOP Spring的AOP的底层实现原理; 为什么jdk动态代理是必须是接口 两种动态代理的区别 AOP实现方式:aop注解或者xml配置;后来工具jar包aspects; aop的属性 事务 事务编码方式: 事务注意事项; 为什么同一个类A调用b方法事务,A方法一定要有事务(编码式的不用) @transaction多个数据源事务怎么指定数据源 传播特性有几种?7种; 某一个事务嵌套另一个事务的时候怎么办? REQUIRED_NEW和REQUIRED区别 Spring的事务是如何回滚的,实现原理; 抽象类和接口的区别,什么时候用抽象类什么时候用接口; StringBuilder和StringBuffer的区别 java值传递和引用传递
【源码】mysql版本_spring3.0 系统模块 1. 组织管理:角色管理,分角色组和成员,有组权限和成员权限。 2. 系统用户:对各个基本的组会员增删改查,单发、群发邮件短信,导入导出excel表格,批量删除 3. 会员管理:对前台用户管理,分配会员级别,到期时间,状态,联系信息等资料 4. 菜单管理:增删改查菜单 ztree(自定义菜单)业务菜单和系统菜单分离 5. 数据字典:无限级别,支持多级别无限分类。内设编号,排序等 6. 系统设置:修改系统名称,邮件服务器配置,短信账号设置,图片水印配置,微信配置 7. 代码生成:打开代码生成器模块 8. 图库管理:对批量上传的图片统一管理 9. 性能监控:监控整个系统的性能,SQL监控,SQL防火墙,URL监控,SPRING监控,SESSION监控等 10. 接口测试:POST or GET 方式检测系统接口,参数加密,json返回结果,计算服务器响应时间 11. 发送邮件:单发,群发邮件 12. 置二维码:生成 or 解析二维码 13.地图工具:经纬度操作 14.即时通讯:打开即时聊天窗口 技术点 1. 导出 导入 excel 文件 2 导出word文件 3. IO 流上传下载文件 4. 群发邮件,可以发html、纯文本格式,可以发给任意邮箱(实现批量发送广告邮件) 5. 群发or单独 发送短信,支持两种第三方短信商接口 6. spring aop 事物处理 7. 代码生成器 (freemarker), 代码 zip 压缩打包 8. MD5加密 SHA加密(登录密码用此加密) 9. 数据库连接池 阿里的 druid。Druid在监控、可扩展性、稳定性和性能方面都有明显的优势,支持并发 10.加入安全框架 shiro (登录授权)(session管理) 11.根据汉字 解析汉字的全拼(拼音)和首字母(导入excel到用户表,根据用户的汉字姓名生成拼音的用户名) 12.app接口(支持与其它语言数据交互) 12.极光推送 (推送给APP及时消息,APP不启动也能收到) 14.微信接口(身份验证,文本、图文回复等) 微信远程控制服务器重启、锁定、其它应用程序 15.java Quartz 定时器 (定时执行某程序,精确到秒,可设置周期) 16.java websocket 即时通讯技术,点对点,群聊,单聊,EXT4对话框 17.新增Lucene全文检索 18.Base64传输图片 19.图片加水印(图片水印,文字水印) 20.生成 or 解析 二维码 21.HTML5 + JAVAEE WebSocket 通信技术 22.批量异步上传图片,可预览,有进度条,支持拖拽上传(百度webuploader )。列表动态滑动放大展示。 23.ehcache 自定义二级缓存 ,选择缓存存放目录,处理并发,增加系统性能 24.服务器内部GET POST 请求 25.uploadify 上传插件,单条、批量上传,带进度条,异步,图片、视频, 其它文件格式均可上传 26.地图选点获取经纬度坐标,根据俩经纬度计算距离

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值