當前位置:首頁 » 文件管理 » 二級緩存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 瀏覽:977
python中的init方法 發布:2025-10-20 08:17:33 瀏覽:686
圖案密碼什麼意思 發布:2025-10-20 08:16:56 瀏覽:838
怎麼清理微信視頻緩存 發布: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