当前位置:首页 » 文件管理 » 二级缓存spring循环

二级缓存spring循环

发布时间: 2023-01-30 08:40:31

1. spring一级缓存和二级缓存的区别是什么

一级缓存:x0dx0a就是Session级别的缓存。一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中。x0dx0a如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。x0dx0a它是内置的事务范围的缓存,不能被卸载。x0dx0a二级缓存:x0dx0a就是SessionFactory级别的缓存。顾名思义,就是查询的时候会把查询结果缓存到二级缓存中。x0dx0a如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库。x0dx0a这是可选的插件式的缓存,在默认情况下,SessionFactory不会启用这个插件。x0dx0a可以在每个类或每个集合的粒度上配置。缓存适配器用于把具体的缓存实现软件与Hibernate集成。x0dx0a严格意义上说,SessionFactory缓存分为两类:内置缓存和外置缓存。我们通常意义上说的二级缓存是指外置缓存。x0dx0a内置缓存与session级别缓存实现方式相似。前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据x0dx0aSessionFactory的内置缓存中存放了映射元数据和预定义sql语句。x0dx0a映射元数据是映射文件中数据的拷贝;x0dx0a而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来。x0dx0aSessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。x0dx0aHibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝。x0dx0a缓存的两个特性:x0dx0a缓存的范围x0dx0a缓存的并发访问策略x0dx0a1、缓存的范围x0dx0a决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。x0dx0a事务范围x0dx0a进程范围x0dx0a集群范围x0dx0a注:x0dx0a对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。x0dx0a事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。x0dx0a2、缓存的并发访问策略x0dx0a当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。x0dx0a在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。x0dx0a因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。x0dx0a事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。x0dx0aA 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。x0dx0a对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。x0dx0aB 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。x0dx0a对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。x0dx0aC 非严格读写型:不保证缓存与数据库中数据的一致性。x0dx0a如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。x0dx0a对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。x0dx0aD 只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。x0dx0a什么样的数据适合存放到第二级缓存中?x0dx0a1、很少被修改的数据x0dx0a2、不是很重要的数据,允许出现偶尔并发的数据x0dx0a3、不会被并发访问的数据x0dx0a4、参考数据x0dx0a不适合存放到第二级缓存的数据?x0dx0a1、经常被修改的数据x0dx0a2、财务数据,绝对不允许出现并发x0dx0a3、与其他应用共享的数据。x0dx0aHibernate的二级缓存策略的一般过程如下:x0dx0a1) 条件查询的时候,总是发出一条select * from table_name where ?. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。x0dx0a2) 把获得的所有数据对象根据ID放入到第二级缓存中。x0dx0a3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。x0dx0a4) 删除、更新、增加数据的时候,同时更新缓存。x0dx0a注:x0dx0aHibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。x0dx0aQuery缓存策略的过程如下:x0dx0a1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。x0dx0a2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。x0dx0a3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。

2. 彻底理解Spring如何解决循环依赖

可以简化为以下5步。

1、构建BeanDefinition

2、实例化 Instantiation

3、属性赋值 Populate

4、初始化 Initialization(BeanPostprocessor -> Aware,init)

5、销毁 Destruction

用来保存实例化、初始化都完成的bean对象。

用来保存实例化完成,但是未初始化完成的对象(这个对象不一定是原始对象,也有可能是经过AOP生成的代理对象)。

用来保存一个对象工厂(ObjectFactory),提供一个匿名内部类,用于创建二级缓存中的对象。

三级缓存中提到的ObjectFactory即 () -> getEarlyBeanReference(beanName,mbd,bean),其中bean就是原始对象。

其中 getEarlyBeanReference 方法是 接口中定义的,AbstractAutoProxyCreator(Spring AOP proxy creator)实现了该方法。

分别按照一级缓存、二级缓存、三级缓存顺序加载。如果存在循环依赖(比如beanName:B依赖beanName:A),而且三级缓存中存在beanName:A的引用,则从三级缓存中拿到beanName:A对应的提早曝光的对象(可能是原始对象,也可能是代理对象)并放入二级缓存。比如又有beanName:C依赖beanName:A,会直接从二级缓存中获取到。

直接添加到一级缓存

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(beanName:A, allowEarlyReference:false)

@Transactional使用的是自动代理创建器AbstractAutoProxyCreator,它实现了getEarlyBeanReference()方法从而很好的对循环依赖提供了支持。

@Async的代理创建使用的是单独的后置处理器实现的,它只在一处()实现了对代理对象的创建,因此若出现它被循环依赖了,就会报。

具体原因分析参考: https://blog.csdn.net/f641385712/article/details/92797058

1、Spring 解决循环依赖有两个前提条件:不全是构造器方式的循环依赖,必须是单例。

2、如果没有出现循环依赖,第三级缓存(singletonFactories)将不会使用到,对象会按照Spring创建bean的生命周期流程,最后将bean直接放到第一级缓存(singletonObjects)中。

3、一定要三级缓存嘛,二级缓存不能解决循环依赖?不能,主要是为了生成代理对象。

因为三级缓存中放的是生成具体对象的匿名内部类(ObjectFactory),它可能生成代理对象,也可能是普通的实例对象。使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象, BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,无法保证程多线程环境下获取到bean对象一致性。

3. hibernate二级缓存 和 spring整合的缓存(就是用哪个Cacheable注解的)有什么区别么

二级缓存配置(spring+hibernate)

说明:本人不建议使用查询缓存,因为查询缓存要求完全相同的查询sql语句才会起作用,所说的查询缓存是针对第二次查询时 sql语句与第一次sql语句完全相同 那么就可以从缓存中取数据而不去数据库中取数据了,在不启用查询缓存的情况下 每次的查询数据也会缓存到二级缓存的 只不过每次查询都会去查询数据库(不包括根据ID查询),启用查询缓存很麻烦 需要每次查询时 调用Query.setCacheable(true)方法才可以,如:List<OrgiData> orgiDatas = (List<OrgiData>) s.createQuery("from OrgiData").setCacheable(true).list();

因此建议将查询缓存设置为如下:
hibernate.cache.use_query_cache=false

还有就是最重要的一点:对于经常修改或重要的数据不宜进行缓存,因为多并发时会造成数据不同步的情况。

首先增加ehcache-1.4.1.jar和backport-util-concurrent-3.1.jar或oscache-2.1.jar

一、spring配置

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>com/handpay/core/merchant/bean/MerchGroupBuy.hbm.xml
</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.SQLServerDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=false
hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider </value>
</property>
</bean>

<!---红色字体是二级缓存相关的设置->

二、hbm.xml文件示例

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.handpay.core.merchant.bean">
<class name="MerchGroupBuy" table="merch_group_buy">
<cache usage="read-write" region="com.handpay.core.merchant.bean.MerchGroupBuy"/>
<id name="id">
<generator class="native" />
</id>
<property name="code" />
<property name="createTime"/>
<property name="minNum"/>
<property name="status">
</property>
<property name="title"/>
<property name="typeCode"/>
<property name="updateTime"/>
</class>
</hibernate-mapping>

三、注解示例

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Table(name = "alcor_t_countries", catalog = "alcorweb")
public class AlcorTCountries implements java.io.Serializable{。。。。}

四、配置文件参数详解

ehcache.xml是ehcache的配置文件,并且存放在应用的classpath中。下面是对该XML文件中的一些元素及其属性的相关说明:

<diskStore>元素:指定一个文件目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下。 下面的参数这样解释:

user.home – 用户主目录

user.dir – 用户当前工作目录

java.io.tmpdir – 默认临时文件路径

<defaultCache>元素:设定缓存的默认数据过期策略。

<cache>元素:设定具体的命名缓存的数据过期策略。

<cache>元素的属性

name:缓存名称。通常为缓存对象的类名(非严格标准)。

maxElementsInMemory:设置基于内存的缓存可存放对象的最大数目。

maxElementsOnDisk:设置基于硬盘的缓存可存放对象的最大数目。

eternal:如果为true,表示对象永远不会过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false;

timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态。

timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期。当对象过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义。

overflowToDisk:如果为true,表示当基于内存的缓存中的对象数目达到了maxElementsInMemory界限后,会把益出的对象写到基于硬盘的缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。

memoryStoreEvictionPolicy:缓存对象清除策略。有三种:

1 FIFO ,first in first out ,这个是大家最熟的,先进先出,不多讲了

2 LFU , Less Frequently Used ,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。

2 LRU ,Least Recently Used ,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

五 、查看 二级缓存数据

1、使用sessionFactory直接获取
Map cacheEntries = sessionFactory().getStatistics()
.getSecondLevelCacheStatistics("cacheRegionName")
.getEntries();

其中 cacheRegionName 既是 ehcache.xml配置中的<cache 标签的name属性值

2、让log4j打印缓存信息(生成环境下请注释掉,以免影响性能)
log4j.logger.org.hibernate.cache=debug

4. spring为什么要使用三级缓存解决循环依赖

首先清楚spring中bean 的加载过程:

1 解析需要spring管理的类为beanDefinition

2 通过反射实例化对象

3 反射设置属性

4初始化,调用initMethod等。(postConstruct也是在这执行)

循环依赖的问题: a依赖b,b依赖a。

在a实例化之后会先将a放入到缓存中,然后给a设置属性,去缓存中查到b。此时找不到就开始b的创建。b实例化之后,放入到缓存中,需要给a设置属性,此时去缓存中查到a设置成功。然后初始化。成功后将b放入一级缓存。这个时候a在给自己属性b设置值的时候就找到了b,然后设置b。完成属性设置,再初始化,初始化后a放入一级缓存。

解决代理对象(如aop)循环依赖的问题。

例: a依赖b,b依赖a,同时a,b都被aop增强。

首先明确aop的实现是通过 postBeanProcess后置处理器,在初始化之后做代理操作的。

为什么使用三级缓存原因:

1 只使用二级缓存,且二级缓存缓存的是一个不完整的bean

如果只使用二级缓存,且二级缓存缓存的是一个不完整的bean,这个时候a在设置属性的过程中去获取b(这个时候a还没有被aop的后置处理器增强),创建b的过程中,b依赖a,b去缓存中拿a拿到的是没有经过代理的a。就有问题。

2 使用二级缓存,且二级缓存是一个工厂方法的缓存

如果二级缓存是一个工厂的缓存,在从缓存中获取的时候获取到经过aop增强的对象。可以看到从工厂缓存中获取的逻辑。

protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

Object exposedObject = bean;

  if (!mbd.isSynthetic() && ()) {

for (BeanPostProcessor bp : getBeanPostProcessors()) {

if (bpinstanceof ) {

ibp = () bp;

            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

        }

}

}

return exposedObject;

}

a依赖b,b依赖a,c。c又依赖a。a,b,c均aop增强。

加载开始: a实例化,放入工厂缓存,设置b,b实例化,设置属性,拿到a,此时从工厂缓存中拿到代理后的a。由于a没加载完毕,不会放入一级缓存。这个时候b开始设置c,c实例化,设置属性a,又去工厂缓存中拿对象a。这个时候拿到的a和b从工厂缓存不是一个对象。出现问题。

3 使用二级缓存,二级缓存缓存的是增强后的bean。这个与spring加载流程不符合。spring加载流程是:实例化,设置属性,初始化,增强。在有循环引用的时候,之前的bean并不会增强后放入到二级缓存。

综上1,2,3 可知二级缓存解决不了有aop的循环依赖。spring采用了三级缓存。

一级缓存  singletonObjects    缓存加载完成的bean。

二级缓存  earlySingletonObjects 缓存从三级缓存中获取到的bean,此时里面的bean没有加载完毕。

三级缓存 singletonFactories 。缓存一个objectFactory工厂。

场景:a依赖b,b依赖a和c,c依赖a。并且a,b,c都aop增强。

加载过程:

a实例化,放入三级工厂缓存,设置属性b,b实例化放入三级缓存。b设置属性a,从三级工厂缓存中获取代理后的对象a,同时,代理后的a放入二级缓存,然后设置属性c,c实例化放入三级缓存,设置属性a,此时从二级缓存中获取到的代理后的a跟b中的a是一个对象,属性a设置成功。c初始化,然后执行后置处理器。进行aop的增强。增强后将代理的c放入到一级缓存,同时删除三级缓存中的c。c加载完成,b得到c,b设置c成功。b初始化,然后执行后置处理器,进行aop增强,将增强后的代理对象b放入到一级缓存。删除三级缓存中的b。此时 a拿到b,设置属性b成功,开始初始化,初始化后执行后置处理器。在aop的后置处理器中有一个以beanName为key,经过aop增强的代理对象为value的map earlyProxyReferences。

这个时候 后置处理器处理对象a的时候,

public (@Nullable Object bean, String beanName) {

if (bean !=null) {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

      if (this.earlyProxyReferences.remove(cacheKey) != bean) {

return wrapIfNecessary(bean, beanName, cacheKey);

      }

}

return bean;

}

也就是 发现这个beanName已经被代理后就不在代理。这个时候执行后置处理器后,a还是未经代理的对象a。此时a再通过getSingleton 重新从缓存中获取一下a。

Object earlySingletonReference = getSingleton(beanName, false);

false 表示不从三级缓存中取,只从一级,二级缓存中获取。

这个时候能拿到二级缓存中的a。二级缓存中的a也是经过代理后的a。

然后将代理后的a放入到一级缓存中。a加载完毕。

放入一级缓存的过程 :

addSingleton(beanName, singletonObject);

从三级工厂缓存中获取对象:

protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

Object exposedObject = bean;

  if (!mbd.isSynthetic() && ()) {

for (BeanPostProcessor bp : getBeanPostProcessors()) {

if (bpinstanceof ) {

ibp = () bp;

            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

        }

}

}

return exposedObject;

}

其中 AbstractAutoProxyCreator实现该接口。

public ObjectgetEarlyBeanReference(Object bean, String beanName) {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

  this.earlyProxyReferences.put(cacheKey, bean);

  return wrapIfNecessary(bean, beanName, cacheKey);

}

wrapIfNecessary()就是真正执行代理的。

bean初始化之后执行的后置处理器:

其中AbstractAutoProxyCreator 实现了该接口。

5. Spring是如何解决循环依赖的

你需要我,我需要你就是循环依赖

在Spring中使用的三级缓存来解决循环依赖问题,这里的缓存其实就是Map对象

当获取一个Bean时会先从缓存中查找是否有相应的Bean。

1 创建A实例

2 将A实例(半初始化,属性没有填充)暴露放入缓存中

3 填充A实例的属性

4 A实例属性依赖B对象

5 创建B对象实例

6 填充B实例属性

7 B实例属性依赖A对象

8 将上面已经暴露到三级缓存中的A对象注入给B实例

在获取A对象的时候执行上面27.1中的getSingleton方法,会将三级缓存中A这个半初始化状态的对象移除,将其存入到二级缓存中。

9 B实例Bean的创建工作继续执行初始化方法

B如果需要AOP代理?最终B对象是个代理对象。B到此就完全的初始化完了,B的依赖对象A此时是个半初始化状态的对象

10 B实例对象保存到一级缓存

最终B实例创建,初始化都执行完后会将自身加入到一级缓存同时清除二级,三级缓存

11 A实例Bean创建继续执行

如果B是被AOP代理的那么此时的A实例注入的B对象就是一个代理对象。

12 A实例Bean执行初始化方法

13 A继续执行上面的10步骤

三级缓存解决问题:循环依赖+AOP问题

只用一,二级缓存:

从上面罗列的步骤看似乎很是完美解决了循环依赖问题,接下来我们看看加入AOP的场景

假如A,B两个对象最终都是要被AOP代理的

执行到这里,A中依赖的B是代理对象没有问题,但是B中依赖的A对象是原始对象;这就不正确了应该依赖的A也必须是代理对象才是。

引入三级缓存:

三级缓存引入了ObjectFactory对象,在获取对象的时候,是调用ObjectFactory#getObject方法。

而这个getObject方法的实现实际执行的是getEarlyBeanReference方法,再来回顾下:

在创建实例时先将其存入三级缓存中:

getEarlyBeanReference方法就是提前创建代理对象

如果开启了AOP代理后

通过getEarlyBeanReference方法提前创建代理对象。这样就解决了循环依赖时AOP代理问题。保证获取的都是同一个对象。

其实引入三级缓存还解决了一个问题就是延迟代理对象的创建,如果不应用ObjectFactory方式那么我们需要不管需不需要都要先创建代理对象,而引入ObjectFactory可以在注入的时候先暴露的是ObjectFactory只有在调用getObject方法的时候才去创建真正的代理对象(避免了所有Bean都强制创建代理对象)。当没有被代理时可以直接返回原始对象,如果被代理会提前创建代理对象。

不用二级直接是用一,三级缓存?

假设场景:A 依赖 B,B 依赖 A、C,C 依赖 A

如果这样会出现不同的代理对象,每次调用getObject都会创建不同的代理对象(在上面的场景中如果只用一,三级缓存那么 B 依赖 A会通过getObject获取一个代理对象Proxy$1,接着注入C的时候 C中又依赖A,那这时候又从getObject获取对象那么返回的将又会是一个新的代理对象Proxy$2;在这个过程中A对象就出现了2个不一样的对象了,这肯定是错误的)。而引入二级缓存也就解决了这个问题。只有二级缓存没有的时候才从三级缓存汇总获取(如果需要则创建代理对象,然后保存到二级缓存中,二级缓存中已经是提前创建了代理对象(如果需要代理))。

当一个Bean完全的创建完以后放入一级缓存中,此时会吧二级三级中的缓存清除。


完毕!!!!

SpringMVC参数统一验证方法
SpringBoot多数据源配置详解
SpringCloud Nacos 整合feign
Spring AOP动态代理失效的解决方法@Transactional为何会失效
SpringBoot配置文件你了解多少?
SpringBoot邮件发送示例 Springboot面试题整理附答案
SpringBoot项目查看线上日志
在Spring Cloud 中你还在使用Ribbon快来试试Load-Balancer
SpringBoot分库分表sharding-sphere3

6. Spring如何处理循环引用

以上就是典型的循环引用场景。

熟悉Spring的人都知道Spring是通过三级缓存来处理循环依赖:

下面是本次跟踪的大概流程,主要是关注了三级缓存的变化情况

用大白话来描述一下流程(假定beanA先创建):

Code1:
从Spring真正开始创建对象的 doGetBean 方法开始

Code2:
此时,由于beanA第一次被创建,因此 一级缓存 获取不到对象,返回NULL,再次返回Code1创建beanA

Code3:(注意这里重载了Code2的方法)
首先从一级缓存中尝试获取beanA,如果获取不到则标记当前beanA处于创建状态,通过 ObjectFactory 开始创建beanA

Code4:
首先通过推断出来的构造器创建beanA,接着将beanA放入三级缓存,开始为beanA填充属性,此时会发现beanB尚未创建,进入beanB的创建流程

beanA填充beanB属性完毕,从一、二级缓存中尝试获取缓存中的beanA

Code5:
创建beanB的流程其实类似上面创建beanA的流程,首先在一、二、三级缓存中没有找到beanB,则开始创建beanB

Code6:
这个其实就上面创建beanA的时候执行过得Code2,先从一级缓存获取beanB,没有获取到则标记beanB正在创建,进入创建beanB的流程

beanB经历 实例化、属性填充、初始化 操作之后,清除beanB正在创建标记,并将beanB从 三级缓存 挪到 一级缓存

Code7:
这里与创建beanA过程的Code4一样,创建beanB完毕之后,开始为beanB填充属性beanA

为beanB填充完毕beanA,beanB的创建过程主要步骤( 实例化、属性填充、初始化 )结束

Code8:
为beanB填充属性beanA的时候,Spring再次尝试创建beanA,此时会走到Code1的流程,但是此时Spring已经可以从三级缓存中获取到beanA,因此不会再往下执行beanA的创建,同时会将beanA从 三级缓存 移动到 二级缓存

PS.
经过beanA和beanB的创建,我们发现Spring每次创建一个bean都会尝试先去 一、二、三级缓存 获取,这里其实就是Code2中 getSingleton ,如果一级缓存没有获取到,但是bean正在被创建则尝试去二级缓存获取,二级缓存没有获取到,但是bean允许提前引用则再次尝试从三级缓存获取

Code9:
beanA经历 创建、属性填充、初始化 完毕,重新尝试从缓存中获取

从上面解决循环引用的问题,我们可以总结出,Spring创建对象、装配属性的时候都尝试先从一、二、三级缓存加载创建好的对象,在装配属性的时候如果从三级缓存获取到对象,会移动到二级缓存,此后获取这个对象就会先从二级缓存中获取。

如果采用二级缓存,单纯从解决循环引用的角度来说确实可以实现,就以上面的beanA来说,假设beanB通过二级缓存装配了beanA属性,后续beanA被某些操作重新包装了一次再次放入二级缓存,就会导致同一个beanA属性,在不同的bean中引用了不同的对象。

7. Spring的三级缓存

例如:A依赖B , B 依赖 C , C 依赖 A 这样就是一个简单的循环依赖。
创建bean的流程为:
一级缓存:singletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories
1、创建A的时候 去一级缓存中查询A对象 为 null,这时会继续将A的实例创建出来(调用构造方法),未进行初始化,创建半成品A,将半成品A 放入到 三级缓存中。
2、初始化A的时候发现需要B,但是B未创建,这时走创建B的流程,这时会继续将B的实例创建出来(调用构造方法),未进行初始化,创建半成品B,将半成品B放入到三级缓存中。
3、初始化B的时候发现需要C,但是C未创建,这时走创建C的流程,这时会继续将C的实例创建出来(调用构造方法),未进行初始化,创建半成品C,将半成品C放入到三级缓冲中。
4、接下来初始化C发现需要A,从一级缓存和二级缓存中取不到A,但是从三级缓冲中取到A,将A放入到二级缓存并从三级缓存中清除。
5、C对象创建成功放入到一级缓存,C对象创建好了以后B对象也可以初始化成功,再将B对象放入一级缓存。
6、最后初始化A对象,放入一级缓存。

二级缓存可以解决循环依赖的问题,但是不能解决aop场景下的循环依赖,这样注入到其他bean的都是原始对象,而不是最终的代理对象。

8. Spring boot + Mybatis plus + Redis实现二级缓存

 1.1   通过application.yml配置redis的连接信息,springboot默认redis用的lecttuce客户端,如果想用jedis的话,只需要在pom.xml中引入redis的时候排除在lecttuce,然后再导入jedis的jar包就好了,

1.2 打开mybatis plus的二级缓存,为true的时候是开启的,false是关闭二级缓存

1.3 编写缓存类继承Cache类,实现Cache中的方法

1.4 早*.xml中加上<cache>标签,type写你所编写二级缓存类的路径

9. spring一级缓存和二级缓存的区别是什么

一级缓存:
就是Session级别的缓存。一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中。
如果短时间内这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。
它是内置的事务范围的缓存,不能被卸载。
二级缓存:
就是SessionFactory级别的缓存。顾名思义,就是查询的时候会把查询结果缓存到二级缓存中。
如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库。
这是可选的插件式的缓存,在默认情况下,SessionFactory不会启用这个插件。
可以在每个类或每个集合的粒度上配置。缓存适配器用于把具体的缓存实现软件与Hibernate集成。
严格意义上说,SessionFactory缓存分为两类:内置缓存和外置缓存。我们通常意义上说的二级缓存是指外置缓存。
内置缓存与session级别缓存实现方式相似。前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句。
映射元数据是映射文件中数据的拷贝;
而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来。
SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝。
缓存的两个特性:
缓存的范围
缓存的并发访问策略
1、缓存的范围
决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
事务范围
进程范围
集群范围
注:
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
2、缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。
因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
A 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
B 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
C 非严格读写型:不保证缓存与数据库中数据的一致性。
如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。
对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
D 只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
注:
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。

10. Spring循环依赖引出的问题(转)

源起

在开发过程中,遇到需要把方法调用改为异步的情况,本来以为简单得加个@Asyn在方法上就行了,没想到项目启动的时候报了如下的错误:

Caused by: org.springframework.beans.factory.:

Error creating bean with name 'customerServiceImpl':

Bean with name 'customerServiceImpl' has been injected into other beans [customerServiceImpl,followServiceImpl,cupidService] in its raw version as part of a circular reference,

but has eventually been wrapped. This means that said other beans do not use the final version of the bean.

This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

看了下好像报的是循环依赖的错误,但是Spring单例是支持循环依赖的,当时一脸懵逼。

拿着报错去网络了下,说是多个动态代理导致的循环依赖报错,也找到了报错的地点,但是还是不明白为什么会这样,所以打算深入源码探个究竟,顺便回顾下Bean的获取流程和循环依赖的内容。

模拟场景

用SpringBoot新建一个demo项目,因为原项目是有定义切面的,这里也定义一个切面:

@Aspect

@Component

public class TestAspect {

    @Pointcut("execution(public * com.example.demo.service.CyclicDependencyService.sameClassMethod(..))")

    private void testPointcut() {}

    @AfterReturning("testPointcut()")

    public void after(JoinPoint point) {

        System.out.println("在" + point.getSignature() + "之后干点事情");

    }

}

然后新建一个注入自己的Service构成循环依赖,然后提供一个方法满足切点要求,并且加上@Async注解:

@Service

public class CyclicDependencyService {

    @Autowired

    private CyclicDependencyService cyclicDependencyService;

    public void test() {

        System.out.println("调用同类方法");

        cyclicDependencyService.sameClassMethod();

    }

    @Async

    public void sameClassMethod() {

        System.out.println("循环依赖中的异步方法");

        System.out.println("方法线程:" + Thread.currentThread().getName());

    }

}

还有别忘了给Application启动类加上@EnableAsync和@EnableAspectJAutoProxy:

@EnableAsync

@EnableAspectJAutoProxy

@SpringBootApplication

public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);

    }

}

最后打好断点,开始debug。

debug

从Bean创建的的起点--AbstractBeanFactory#getBean开始

// Eagerly check singleton cache for manually registered singletons.

Object sharedInstance = getSingleton(beanName);

首先会在缓存中查找,DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference):

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

  Object singletonObject = this.singletonObjects.get(beanName);

  if (singletonObject == null && (beanName)) {

      synchronized (this.singletonObjects) {

        singletonObject = this.earlySingletonObjects.get(beanName);

        if (singletonObject == null && allowEarlyReference) {

            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

            if (singletonFactory != null) {

              singletonObject = singletonFactory.getObject();

              this.earlySingletonObjects.put(beanName, singletonObject);

              this.singletonFactories.remove(beanName);

            }

        }

      }

  }

  return singletonObject;

}

这里一共有三级缓存:

singletonObjects,保存初始化完成的单例bean实例;

earlySingletonObjects,保存提前曝光的单例bean实例;

singletonFactories,保存单例bean的工厂函数对象;

后面两级都是为了解决循环依赖设置的,具体查找逻辑在后续其他情况下调用会说明。

缓存中找不到,就要创建单例:

sharedInstance = getSingleton(beanName, () -> {

  try {

      return createBean(beanName, mbd, args);

  }

  catch (BeansException ex) {

      // Explicitly remove instance from singleton cache: It might have been put there

      // eagerly by the creation process, to allow for circular reference resolution.

      // Also remove any beans that received a temporary reference to the bean.

      destroySingleton(beanName);

      throw ex;

  }

});

调用DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory):

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

    ...

    beforeSingletonCreation(beanName);

    ...

    singletonObject = singletonFactory.getObject();

    ...

    afterSingletonCreation(beanName);

    ...

    addSingleton(beanName, singletonObject);

    ...

}

创建前后分别做了这几件事:

前,beanName放入singletonsCurrentlyInCreation,表示单例正在创建中

后,从singletonsCurrentlyInCreation中移除beanName

后,将创建好的bean放入singletonObjects,移除在singletonFactories和earlySingletonObjects的对象

创建单例调用getSingleton时传入的工厂函数对象的getObject方法,实际上就是createBean方法,主要逻辑在#doCreateBean中:

...

instanceWrapper = createBeanInstance(beanName, mbd, args);

final Object bean = instanceWrapper.getWrappedInstance();

...

// Eagerly cache singletons to be able to resolve circular references

// even when triggered by lifecycle interfaces like BeanFactoryAware.

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

      (beanName));

if (earlySingletonExposure) {

  if (logger.isTraceEnabled()) {

      logger.trace("Eagerly caching bean '" + beanName +

            "' to allow for resolving potential circular references");

  }

  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

}

// Initialize the bean instance.

Object exposedObject = bean;

try {

  populateBean(beanName, mbd, instanceWrapper);

  exposedObject = initializeBean(beanName, exposedObject, mbd);

}

...

if (earlySingletonExposure) {

  Object earlySingletonReference = getSingleton(beanName, false);

  if (earlySingletonReference != null) {

      if (exposedObject == bean) {

        exposedObject = earlySingletonReference;

      }

      else if (!this. && hasDependentBean(beanName)) {

        String[] dependentBeans = getDependentBeans(beanName);

        Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

        for (String dependentBean : dependentBeans) {

            if (!(dependentBean)) {

              actualDependentBeans.add(dependentBean);

            }

        }

        if (!actualDependentBeans.isEmpty()) {

            throw new (beanName,

                  "Bean with name '" + beanName + "' has been injected into other beans [" +

                  StringUtils.(actualDependentBeans) +

                  "] in its raw version as part of a circular reference, but has eventually been " +

                  "wrapped. This means that said other beans do not use the final version of the " +

                  "bean. This is often the result of over-eager type matching - consider using " +

                  "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");

        }

      }

  }

}

可以看到报错就是在这个方法里抛出的,那么这个方法就是重点中的重点。

首先实例化单例,instantiate,只是实例化获取对象引用,还没有注入依赖。我debug时记录的bean对象是 CyclicDependencyService@4509 ;

然后判断bean是否需要提前暴露,需要满足三个条件:1、是单例;2、支持循环依赖;3、bean正在创建中,也就是到前面提到的singletonsCurrentlyInCreation中能查找到,全满足的话就会调用DefaultSingletonBeanRegistry#addSingletonFactory把beanName和单例工厂函数对象(匿名实现调用#getEarlyBeanReference方法)放入singletonFactories;

接着就是注入依赖,填充属性,具体怎么注入这里就不展开了,最后会为属性cyclicDependencyService调用DefaultSingletonBeanRegistry.getSingleton(beanName, true),注意这里和最开始的那次调用不一样,为true,就会在singletonFactories中找到bean的单例工厂函数对象,也就是在上一步提前暴露时放入的,然后调用它的匿名实现#getEarlyBeanReference:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

  Object exposedObject = bean;

  if (!mbd.isSynthetic() && ()) {

      for (BeanPostProcessor bp : getBeanPostProcessors()) {

        if (bp instanceof ) {

            ibp = () bp;

            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

        }

      }

  }

  return exposedObject;

}

方法逻辑就是挨个调用实现了接口的后置处理器(以下简称BBP)的getEarlyBeanReference方法。一个一个debug下来,其他都是原样返回bean,只有会把原bean( CyclicDependencyService@4509 )存在earlyProxyReferences,然后将bean的代理返回(debug时记录的返回对象是 CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740 )并放入earlySingletonObjects,再赋给属性cyclicDependencyService。

public Object getEarlyBeanReference(Object bean, String beanName) {

  Object cacheKey = getCacheKey(bean.getClass(), beanName);

  this.earlyProxyReferences.put(cacheKey, bean);

  return wrapIfNecessary(bean, beanName, cacheKey);

}

属性填充完成后就是调用初始化方法#initializeBean:

...

invokeAwareMethods(beanName, bean);

...

wrappedBean = (wrappedBean, beanName);

...

invokeInitMethods(beanName, wrappedBean, mbd);

...

wrappedBean = (wrappedBean, beanName);

...

初始化主要分为这几步:

如果bean实现了BeanNameAware、BeanClassLoaderAware或BeanFactoryAware,把相应的资源放入bean;

顺序执行BBP的方法;

如果实现了InitializingBean就执行afterPropertiesSet方法,然后执行自己的init-method;

顺序执行BBP的。

debug的时候发现是第4步改变了bean,先执行#:

public Object (@Nullable Object bean, String beanName) {

  if (bean != null) {

      Object cacheKey = getCacheKey(bean.getClass(), beanName);

      if (this.earlyProxyReferences.remove(cacheKey) != bean) {

        return wrapIfNecessary(bean, beanName, cacheKey);

      }

  }

  return bean;

}

这里会获取并移除之前存在earlyProxyReferences的bean( CyclicDependencyService@4509 ),因为和当前bean是同一个对象,所以什么都没做直接返回。随后会执行#:

if (isEligible(bean, beanName)) {

  ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);

  if (!proxyFactory.isProxyTargetClass()) {

      evaluateProxyInterfaces(bean.getClass(), proxyFactory);

  }

  proxyFactory.addAdvisor(this.advisor);

  customizeProxyFactory(proxyFactory);

  return proxyFactory.getProxy(getProxyClassLoader());

}

先判断bean是否有需要代理,因为CyclicDependencyService有方法带有@Async注解就需要代理,返回代理对象是 CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273 。

返回的代理对象赋值给#doCreateBean方法内的exposedObject,接下来就到了检查循环依赖的地方了:

if (earlySingletonExposure) {

  Object earlySingletonReference = getSingleton(beanName, false);

  if (earlySingletonReference != null) {

      if (exposedObject == bean) {

        exposedObject = earlySingletonReference;

      }

      else if (!this. && hasDependentBean(beanName)) {

        String[] dependentBeans = getDependentBeans(beanName);

        Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

        for (String dependentBean : dependentBeans) {

            if (!(dependentBean)) {

              actualDependentBeans.add(dependentBean);

            }

        }

        if (!actualDependentBeans.isEmpty()) {

            throw new (beanName,

                  "Bean with name '" + beanName + "' has been injected into other beans [" +

                  StringUtils.(actualDependentBeans) +

                  "] in its raw version as part of a circular reference, but has eventually been " +

                  "wrapped. This means that said other beans do not use the final version of the " +

                  "bean. This is often the result of over-eager type matching - consider using " +

                  "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");

        }

      }

  }

}

首先从earlySingletonObjects里拿到前面属性填充时放入的bean代理( CyclicDependencyService$$EnhancerBySpringCGLIB$$6ed9e2db@4740 ),不为空的话就比较bean和exposedObject,分别是 CyclicDependencyService@4509 和 CyclicDependencyService$$EnhancerBySpringCGLIB$$e66d8f6e@5273 ,很明显不是同一个对象,然后会判断属性和是否有依赖的bean,然后判断这些bean是否是真实依赖的,一旦存在真实依赖的bean,就会抛出。

总结

总结下Spring解决循环依赖的思路:在创建bean时,对于满足提前曝光条件的单例,会把该单例的工厂函数对象放入三级缓存中的singletonFactories中;然后在填充属性时,如果存在循环依赖,必然会尝试获取该单例,也就是执行之前放入的工厂函数的匿名实现,这时候拿到的有可能是原bean对象,也有可能是被某些BBP处理过返回的代理对象,会放入三级缓存中的earlySingletonObjects中;接着bean开始初始化,结果返回的有可能是原bean对象,也有可能是代理对象;最后对于满足提前曝光的单例,如果真的有提前曝光的动作,就会去检查初始化后的bean对象是不是原bean对象是同一个对象,只有不是的情况下才可能抛出异常。重点就在于 存在循环依赖的情况下,初始化过的bean对象是不是跟原bean是同一个对象 。

从以上的debug过程可以看出,是这个BBP在初始化过程中改变了bean,使得结果bean和原bean不是一个对象,而则是在填充属性获取提前曝光的对象时把原始bean缓存起来,返回代理的bean。然后在初始化时执行它的方法时如果传入的bean是之前缓存的原始bean,就直接返回,不进行代理。如果其他BBP也都没有改变bean的话,初始化过后的bean就是跟原始bean是同一个对象,这时就会把提前曝光的对象(代理过的)作为最终生成的bean。

https://segmentfault.com/a/1190000018835760

热点内容
java返回this 发布:2025-10-20 08:28:16 浏览:713
制作脚本网站 发布:2025-10-20 08:17:34 浏览:976
python中的init方法 发布:2025-10-20 08:17:33 浏览:686
图案密码什么意思 发布:2025-10-20 08:16:56 浏览:837
怎么清理微信视频缓存 发布:2025-10-20 08:12:37 浏览:745
c语言编译器怎么看执行过程 发布:2025-10-20 08:00:32 浏览:1085
邮箱如何填写发信服务器 发布:2025-10-20 07:45:27 浏览:314
shell脚本入门案例 发布:2025-10-20 07:44:45 浏览:194
怎么上传照片浏览上传 发布:2025-10-20 07:44:03 浏览:882
python股票数据获取 发布:2025-10-20 07:39:44 浏览:840