日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

注释驱动的 Spring cache 缓存介绍--转载

發(fā)布時間:2025/4/5 javascript 71 豆豆
生活随笔 收集整理的這篇文章主要介紹了 注释驱动的 Spring cache 缓存介绍--转载 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述

Spring 3.1 引入了激動人心的基于注釋(annotation)的緩存(cache)技術(shù),它本質(zhì)上不是一個具體的緩存實現(xiàn)方案(例如 EHCache 或者 OSCache),而是一個對緩存使用的抽象,通過在既有代碼中添加少量它定義的各種 annotation,即能夠達(dá)到緩存方法的返回對象的效果。

Spring 的緩存技術(shù)還具備相當(dāng)?shù)撵`活性,不僅能夠使用 SpEL(Spring Expression Language)來定義緩存的 key 和各種 condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業(yè)緩存例如 EHCache 集成。

其特點總結(jié)如下:

  • 通過少量的配置 annotation 注釋即可使得既有代碼支持緩存
  • 支持開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方組件即可使用緩存
  • 支持 Spring Express Language,能使用對象的任何屬性或者方法來定義緩存的 key 和 condition
  • 支持 AspectJ,并通過其實現(xiàn)任何方法的緩存支持
  • 支持自定義 key 和自定義緩存管理者,具有相當(dāng)?shù)撵`活性和擴(kuò)展性

本文將針對上述特點對 Spring cache 進(jìn)行詳細(xì)的介紹,主要通過一個簡單的例子和原理介紹展開,然后我們將一起看一個比較實際的緩存例子,最后會介紹 spring cache 的使用限制和注意事項。OK,Let ’ s begin!

原來我們是怎么做的

這里先展示一個完全自定義的緩存實現(xiàn),即不用任何第三方的組件來實現(xiàn)某種對象的內(nèi)存緩存。

場景是:對一個賬號查詢方法做緩存,以賬號名稱為 key,賬號對象為 value,當(dāng)以相同的賬號名稱查詢賬號的時候,直接從緩存中返回結(jié)果,否則更新緩存。賬號查詢服務(wù)還支持 reload 緩存(即清空緩存)。

首先定義一個實體類:賬號類,具備基本的 id 和 name 屬性,且具備 getter 和 setter 方法

清單 1. Account.java
package cacheOfAnno; public class Account { private int id; private String name; public Account(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

然后定義一個緩存管理器,這個管理器負(fù)責(zé)實現(xiàn)緩存邏輯,支持對象的增加、修改和刪除,支持值對象的泛型。如下:

清單 2. MyCacheManager.java
package oldcache; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class MyCacheManager<T> { private Map<String,T> cache = new ConcurrentHashMap<String,T>(); public T getValue(Object key) { return cache.get(key); } public void addOrUpdateCache(String key,T value) { cache.put(key, value); } public void evictCache(String key) {// 根據(jù) key 來刪除緩存中的一條記錄if(cache.containsKey(key)) { cache.remove(key); } } public void evictCache() {// 清空緩存中的所有記錄cache.clear(); } }

好,現(xiàn)在我們有了實體類和一個緩存管理器,還需要一個提供賬號查詢的服務(wù)類,此服務(wù)類使用緩存管理器來支持賬號查詢緩存,如下:

清單 3. MyAccountService.java
package oldcache; import cacheOfAnno.Account; public class MyAccountService { private MyCacheManager<Account> cacheManager; public MyAccountService() { cacheManager = new MyCacheManager<Account>();// 構(gòu)造一個緩存管理器} public Account getAccountByName(String acctName) { Account result = cacheManager.getValue(acctName);// 首先查詢緩存if(result!=null) { System.out.println("get from cache..."+acctName); return result;// 如果在緩存中,則直接返回緩存的結(jié)果} result = getFromDB(acctName);// 否則到數(shù)據(jù)庫中查詢if(result!=null) {// 將數(shù)據(jù)庫查詢的結(jié)果更新到緩存中cacheManager.addOrUpdateCache(acctName, result); } return result; } public void reload() { cacheManager.evictCache(); } private Account getFromDB(String acctName) { System.out.println("real querying db..."+acctName); return new Account(acctName); } }

現(xiàn)在我們開始寫一個測試類,用于測試剛才的緩存是否有效

清單 4. Main.java
package oldcache; public class Main { public static void main(String[] args) { MyAccountService s = new MyAccountService(); // 開始查詢賬號s.getAccountByName("somebody");// 第一次查詢,應(yīng)該是數(shù)據(jù)庫查詢s.getAccountByName("somebody");// 第二次查詢,應(yīng)該直接從緩存返回s.reload();// 重置緩存System.out.println("after reload..."); s.getAccountByName("somebody");// 應(yīng)該是數(shù)據(jù)庫查詢s.getAccountByName("somebody");// 第二次查詢,應(yīng)該直接從緩存返回} }

按照分析,執(zhí)行結(jié)果應(yīng)該是:首先從數(shù)據(jù)庫查詢,然后直接返回緩存中的結(jié)果,重置緩存后,應(yīng)該先從數(shù)據(jù)庫查詢,然后返回緩存中的結(jié)果,實際的執(zhí)行結(jié)果如下:

清單 5. 運行結(jié)果
real querying db...somebody// 第一次從數(shù)據(jù)庫加載get from cache...somebody// 第二次從緩存加載after reload...// 清空緩存real querying db...somebody// 又從數(shù)據(jù)庫加載get from cache...somebody// 從緩存加載

可以看出我們的緩存起效了,但是這種自定義的緩存方案有如下劣勢:

  • 緩存代碼和業(yè)務(wù)代碼耦合度太高,如上面的例子,AccountService 中的 getAccountByName()方法中有了太多緩存的邏輯,不便于維護(hù)和變更
  • 不靈活,這種緩存方案不支持按照某種條件的緩存,比如只有某種類型的賬號才需要緩存,這種需求會導(dǎo)致代碼的變更
  • 緩存的存儲這塊寫的比較死,不能靈活的切換為使用第三方的緩存模塊

如果你的代碼中有上述代碼的影子,那么你可以考慮按照下面的介紹來優(yōu)化一下你的代碼結(jié)構(gòu)了,也可以說是簡化,你會發(fā)現(xiàn),你的代碼會變得優(yōu)雅的多!

Hello World,注釋驅(qū)動的 Spring Cache

Hello World 的實現(xiàn)目標(biāo)

本 Hello World 類似于其他任何的 Hello World 程序,從最簡單實用的角度展現(xiàn) spring cache 的魅力,它基于剛才自定義緩存方案的實體類 Account.java,重新定義了 AccountService.java 和測試類 Main.java(注意這個例子不用自己定義緩存管理器,因為 spring 已經(jīng)提供了缺省實現(xiàn))

需要的 jar 包

為了實用 spring cache 緩存方案,在工程的 classpath 必須具備下列 jar 包。

圖 1. 工程依賴的 jar 包圖

注意這里我引入的是最新的 spring 3.2.0.M1 版本 jar 包,其實只要是 spring 3.1 以上,都支持 spring cache。其中 spring-context-*.jar 包含了 cache 需要的類。

定義實體類、服務(wù)類和相關(guān)配置文件

實體類就是上面自定義緩存方案定義的 Account.java,這里重新定義了服務(wù)類,如下:

清單 6. AccountService.java
package cacheOfAnno; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; public class AccountService { @Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCache public Account getAccountByName(String userName) { // 方法內(nèi)部實現(xiàn)不考慮緩存邏輯,直接實現(xiàn)業(yè)務(wù)System.out.println("real query account."+userName); return getFromDB(userName); } private Account getFromDB(String acctName) { System.out.println("real querying db..."+acctName); return new Account(acctName); } }

注意,此類的 getAccountByName 方法上有一個注釋 annotation,即 @Cacheable(value=”accountCache”),這個注釋的意思是,當(dāng)調(diào)用這個方法的時候,會從一個名叫 accountCache 的緩存中查詢,如果沒有,則執(zhí)行實際的方法(即查詢數(shù)據(jù)庫),并將執(zhí)行的結(jié)果存入緩存中,否則返回緩存中的對象。這里的緩存中的 key 就是參數(shù) userName,value 就是 Account 對象。“accountCache”緩存是在 spring*.xml 中定義的名稱。

好,因為加入了 spring,所以我們還需要一個 spring 的配置文件來支持基于注釋的緩存

清單 7. Spring-cache-anno.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:cache="http://www.springframework.org/schema/cache"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /><bean id="accountServiceBean" class="cacheOfAnno.AccountService"/> <!-- generic cache manager --> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"><property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="default" /> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="accountCache" /> </set> </property> </bean> </beans>

注意這個 spring 配置文件有一個關(guān)鍵的支持緩存的配置項:<cache:annotation-driven />,這個配置項缺省使用了一個名字叫 cacheManager 的緩存管理器,這個緩存管理器有一個 spring 的缺省實現(xiàn),即 org.springframework.cache.support.SimpleCacheManager,這個緩存管理器實現(xiàn)了我們剛剛自定義的緩存管理器的邏輯,它需要配置一個屬性 caches,即此緩存管理器管理的緩存集合,除了缺省的名字叫 default 的緩存,我們還自定義了一個名字叫 accountCache 的緩存,使用了缺省的內(nèi)存存儲方案 ConcurrentMapCacheFactoryBean,它是基于 java.util.concurrent.ConcurrentHashMap 的一個內(nèi)存緩存實現(xiàn)方案。

OK,現(xiàn)在我們具備了測試條件,測試代碼如下:

清單 8. Main.java
package cacheOfAnno; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); // 第一次查詢,應(yīng)該走數(shù)據(jù)庫System.out.print("first query..."); s.getAccountByName("somebody"); // 第二次查詢,應(yīng)該不查數(shù)據(jù)庫,直接返回緩存的值System.out.print("second query..."); s.getAccountByName("somebody"); System.out.println(); } }

上面的測試代碼主要進(jìn)行了兩次查詢,第一次應(yīng)該會查詢數(shù)據(jù)庫,第二次應(yīng)該返回緩存,不再查數(shù)據(jù)庫,我們執(zhí)行一下,看看結(jié)果

清單 9. 執(zhí)行結(jié)果
first query...real query account.somebody// 第一次查詢real querying db...somebody// 對數(shù)據(jù)庫進(jìn)行了查詢second query...// 第二次查詢,沒有打印數(shù)據(jù)庫查詢?nèi)罩?#xff0c;直接返回了緩存中的結(jié)果

可以看出我們設(shè)置的基于注釋的緩存起作用了,而在 AccountService.java 的代碼中,我們沒有看到任何的緩存邏輯代碼,只有一行注釋:@Cacheable(value="accountCache"),就實現(xiàn)了基本的緩存方案,是不是很強(qiáng)大?

如何清空緩存

好,到目前為止,我們的 spring cache 緩存程序已經(jīng)運行成功了,但是還不完美,因為還缺少一個重要的緩存管理邏輯:清空緩存,當(dāng)賬號數(shù)據(jù)發(fā)生變更,那么必須要清空某個緩存,另外還需要定期的清空所有緩存,以保證緩存數(shù)據(jù)的可靠性。

為了加入清空緩存的邏輯,我們只要對 AccountService.java 進(jìn)行修改,從業(yè)務(wù)邏輯的角度上看,它有兩個需要清空緩存的地方

  • 當(dāng)外部調(diào)用更新了賬號,則我們需要更新此賬號對應(yīng)的緩存
  • 當(dāng)外部調(diào)用說明重新加載,則我們需要清空所有緩存
清單 10. AccountService.java

點擊查看代碼清單

清單 11. Main.java

點擊查看代碼清單

清單 12. 運行結(jié)果
first query...real querying db...somebody second query... start testing clear cache... real querying db...somebody1 real querying db...somebody2 real update db...somebody1 real querying db...somebody1 real querying db...somebody1 real querying db...somebody2

結(jié)果和我們期望的一致,所以,我們可以看出,spring cache 清空緩存的方法很簡單,就是通過 @CacheEvict 注釋來標(biāo)記要清空緩存的方法,當(dāng)這個方法被調(diào)用后,即會清空緩存。注意其中一個 @CacheEvict(value=”accountCache”,key=”#account.getName()”),其中的 Key 是用來指定緩存的 key 的,這里因為我們保存的時候用的是 account 對象的 name 字段,所以這里還需要從參數(shù) account 對象中獲取 name 的值來作為 key,前面的 # 號代表這是一個 SpEL 表達(dá)式,此表達(dá)式可以遍歷方法的參數(shù)對象,具體語法可以參考 Spring 的相關(guān)文檔手冊。

如何按照條件操作緩存

前面介紹的緩存方法,沒有任何條件,即所有對 accountService 對象的 getAccountByName 方法的調(diào)用都會起動緩存效果,不管參數(shù)是什么值,如果有一個需求,就是只有賬號名稱的長度小于等于 4 的情況下,才做緩存,大于 4 的不使用緩存,那怎么實現(xiàn)呢?

Spring cache 提供了一個很好的方法,那就是基于 SpEL 表達(dá)式的 condition 定義,這個 condition 是 @Cacheable 注釋的一個屬性,下面我來演示一下

清單 13. AccountService.java(getAccountByName 方法修訂,支持條件)
@Cacheable(value="accountCache",condition="#userName.length() <= 4")// 緩存名叫 accountCache public Account getAccountByName(String userName) { // 方法內(nèi)部實現(xiàn)不考慮緩存邏輯,直接實現(xiàn)業(yè)務(wù)return getFromDB(userName); }

注意其中的 condition=”#userName.length() <=4”,這里使用了 SpEL 表達(dá)式訪問了參數(shù) userName 對象的 length() 方法,條件表達(dá)式返回一個布爾值,true/false,當(dāng)條件為 true,則進(jìn)行緩存操作,否則直接調(diào)用方法執(zhí)行的返回結(jié)果。

清單 14. 測試方法
s.getAccountByName("somebody");// 長度大于 4,不會被緩存s.getAccountByName("sbd");// 長度小于 4,會被緩存s.getAccountByName("somebody");// 還是查詢數(shù)據(jù)庫s.getAccountByName("sbd");// 會從緩存返回
清單 15. 運行結(jié)果
real querying db...somebody real querying db...sbd real querying db...somebody

可見對長度大于 4 的賬號名 (somebody) 沒有緩存,每次都查詢數(shù)據(jù)庫。

如果有多個參數(shù),如何進(jìn)行 key 的組合

假設(shè) AccountService 現(xiàn)在有一個需求,要求根據(jù)賬號名、密碼和是否發(fā)送日志查詢賬號信息,很明顯,這里我們需要根據(jù)賬號名、密碼對賬號對象進(jìn)行緩存,而第三個參數(shù)“是否發(fā)送日志”對緩存沒有任何影響。所以,我們可以利用 SpEL 表達(dá)式對緩存 key 進(jìn)行設(shè)計

清單 16. Account.java(增加 password 屬性)
private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }
清單 17. AccountService.java(增加 getAccount 方法,支持組合 key)
@Cacheable(value="accountCache",key="#userName.concat(#password)") public Account getAccount(String userName,String password,boolean sendLog) { // 方法內(nèi)部實現(xiàn)不考慮緩存邏輯,直接實現(xiàn)業(yè)務(wù)return getFromDB(userName,password); }

注意上面的 key 屬性,其中引用了方法的兩個參數(shù) userName 和 password,而 sendLog 屬性沒有考慮,因為其對緩存沒有影響。

清單 18. Main.java
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); s.getAccount("somebody", "123456", true);// 應(yīng)該查詢數(shù)據(jù)庫s.getAccount("somebody", "123456", true);// 應(yīng)該走緩存s.getAccount("somebody", "123456", false);// 應(yīng)該走緩存s.getAccount("somebody", "654321", true);// 應(yīng)該查詢數(shù)據(jù)庫s.getAccount("somebody", "654321", true);// 應(yīng)該走緩存}

上述測試,是采用了相同的賬號,不同的密碼組合進(jìn)行查詢,那么一共有兩種組合情況,所以針對數(shù)據(jù)庫的查詢應(yīng)該只有兩次。

清單 19. 運行結(jié)果
real querying db...userName=somebody password=123456 real querying db...userName=somebody password=654321

和我們預(yù)期的一致。

如何做到:既要保證方法被調(diào)用,又希望結(jié)果被緩存

根據(jù)前面的例子,我們知道,如果使用了 @Cacheable 注釋,則當(dāng)重復(fù)使用相同參數(shù)調(diào)用方法的時候,方法本身不會被調(diào)用執(zhí)行,即方法本身被略過了,取而代之的是方法的結(jié)果直接從緩存中找到并返回了。

現(xiàn)實中并不總是如此,有些情況下我們希望方法一定會被調(diào)用,因為其除了返回一個結(jié)果,還做了其他事情,例如記錄日志,調(diào)用接口等,這個時候,我們可以用 @CachePut 注釋,這個注釋可以確保方法被執(zhí)行,同時方法的返回值也被記錄到緩存中。

清單 20. AccountService.java
@Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCache public Account getAccountByName(String userName) { // 方法內(nèi)部實現(xiàn)不考慮緩存邏輯,直接實現(xiàn)業(yè)務(wù)return getFromDB(userName); } @CachePut(value="accountCache",key="#account.getName()")// 更新 accountCache 緩存public Account updateAccount(Account account) { return updateDB(account); } private Account updateDB(Account account) { System.out.println("real updating db..."+account.getName()); return account; }
清單 21. Main.java
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); Account account = s.getAccountByName("someone"); account.setPassword("123"); s.updateAccount(account); account.setPassword("321"); s.updateAccount(account); account = s.getAccountByName("someone"); System.out.println(account.getPassword()); }

如上面的代碼所示,我們首先用 getAccountByName 方法查詢一個人 someone 的賬號,這個時候會查詢數(shù)據(jù)庫一次,但是也記錄到緩存中了。然后我們修改了密碼,調(diào)用了 updateAccount 方法,這個時候會執(zhí)行數(shù)據(jù)庫的更新操作且記錄到緩存,我們再次修改密碼并調(diào)用 updateAccount 方法,然后通過 getAccountByName 方法查詢,這個時候,由于緩存中已經(jīng)有數(shù)據(jù),所以不會查詢數(shù)據(jù)庫,而是直接返回最新的數(shù)據(jù),所以打印的密碼應(yīng)該是“321”

清單 22. 運行結(jié)果
real querying db...someone real updating db...someone real updating db...someone 321

和分析的一樣,只查詢了一次數(shù)據(jù)庫,更新了兩次數(shù)據(jù)庫,最終的結(jié)果是最新的密碼。說明 @CachePut 確實可以保證方法被執(zhí)行,且結(jié)果一定會被緩存。

@Cacheable、@CachePut、@CacheEvict 注釋介紹

通過上面的例子,我們可以看到 spring cache 主要使用兩個注釋標(biāo)簽,即 @Cacheable、@CachePut 和 @CacheEvict,我們總結(jié)一下其作用和配置方法。

表 1. @Cacheable 作用和配置方法
@Cacheable 的作用主要針對方法配置,能夠根據(jù)方法的請求參數(shù)對其結(jié)果進(jìn)行緩存
@Cacheable 主要的參數(shù)
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@Cacheable(value=”mycache”) 或者?
@Cacheable(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達(dá)式編寫,如果不指定,則缺省按照方法的所有參數(shù)進(jìn)行組合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進(jìn)行緩存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 2. @CachePut 作用和配置方法
@CachePut 的作用主要針對方法配置,能夠根據(jù)方法的請求參數(shù)對其結(jié)果進(jìn)行緩存,和 @Cacheable 不同的是,它每次都會觸發(fā)真實方法的調(diào)用
@CachePut 主要的參數(shù)
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@Cacheable(value=”mycache”) 或者?
@Cacheable(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達(dá)式編寫,如果不指定,則缺省按照方法的所有參數(shù)進(jìn)行組合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進(jìn)行緩存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 3. @CacheEvict 作用和配置方法
@CachEvict 的作用主要針對方法配置,能夠根據(jù)一定的條件對緩存進(jìn)行清空
@CacheEvict 主要的參數(shù)
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@CachEvict(value=”mycache”) 或者?
@CachEvict(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達(dá)式編寫,如果不指定,則缺省按照方法的所有參數(shù)進(jìn)行組合例如:
@CachEvict(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才清空緩存例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
allEntries是否清空所有緩存內(nèi)容,缺省為 false,如果指定為 true,則方法調(diào)用后將立即清空所有緩存例如:
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation是否在方法執(zhí)行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執(zhí)行的時候就清空緩存,缺省情況下,如果方法執(zhí)行拋出異常,則不會清空緩存例如:
@CachEvict(value=”testcache”,beforeInvocation=true)

基本原理

和 spring 的事務(wù)管理類似,spring cache 的關(guān)鍵原理就是 spring AOP,通過 spring AOP,其實現(xiàn)了在方法調(diào)用前、調(diào)用后獲取方法的入?yún)⒑头祷刂?#xff0c;進(jìn)而實現(xiàn)了緩存的邏輯。我們來看一下下面這個圖:

圖 2. 原始方法調(diào)用圖

上圖顯示,當(dāng)客戶端“Calling code”調(diào)用一個普通類 Plain Object 的 foo() 方法的時候,是直接作用在 pojo 類自身對象上的,客戶端擁有的是被調(diào)用者的直接的引用。

而 Spring cache 利用了 Spring AOP 的動態(tài)代理技術(shù),即當(dāng)客戶端嘗試調(diào)用 pojo 的 foo()方法的時候,給他的不是 pojo 自身的引用,而是一個動態(tài)生成的代理類

圖 3. 動態(tài)代理調(diào)用圖

如上圖所示,這個時候,實際客戶端擁有的是一個代理的引用,那么在調(diào)用 foo() 方法的時候,會首先調(diào)用 proxy 的 foo() 方法,這個時候 proxy 可以整體控制實際的 pojo.foo() 方法的入?yún)⒑头祷刂?#xff0c;比如緩存結(jié)果,比如直接略過執(zhí)行實際的 foo() 方法等,都是可以輕松做到的。

擴(kuò)展性

直到現(xiàn)在,我們已經(jīng)學(xué)會了如何使用開箱即用的 spring cache,這基本能夠滿足一般應(yīng)用對緩存的需求,但現(xiàn)實總是很復(fù)雜,當(dāng)你的用戶量上去或者性能跟不上,總需要進(jìn)行擴(kuò)展,這個時候你或許對其提供的內(nèi)存緩存不滿意了,因為其不支持高可用性,也不具備持久化數(shù)據(jù)能力,這個時候,你就需要自定義你的緩存方案了,還好,spring 也想到了這一點。

我們先不考慮如何持久化緩存,畢竟這種第三方的實現(xiàn)方案很多,我們要考慮的是,怎么利用 spring 提供的擴(kuò)展點實現(xiàn)我們自己的緩存,且在不改原來已有代碼的情況下進(jìn)行擴(kuò)展。

首先,我們需要提供一個 CacheManager 接口的實現(xiàn),這個接口告訴 spring 有哪些 cache 實例,spring 會根據(jù) cache 的名字查找 cache 的實例。另外還需要自己實現(xiàn) Cache 接口,Cache 接口負(fù)責(zé)實際的緩存邏輯,例如增加鍵值對、存儲、查詢和清空等。利用 Cache 接口,我們可以對接任何第三方的緩存系統(tǒng),例如 EHCache、OSCache,甚至一些內(nèi)存數(shù)據(jù)庫例如 memcache 或者 h2db 等。下面我舉一個簡單的例子說明如何做。

清單 23. MyCacheManager
package cacheOfAnno; import java.util.Collection; import org.springframework.cache.support.AbstractCacheManager; public class MyCacheManager extends AbstractCacheManager { private Collection<? extends MyCache> caches; /** * Specify the collection of Cache instances to use for this CacheManager. */ public void setCaches(Collection<? extends MyCache> caches) { this.caches = caches; } @Override protected Collection<? extends MyCache> loadCaches() { return this.caches; } }

上面的自定義的 CacheManager 實際繼承了 spring 內(nèi)置的 AbstractCacheManager,實際上僅僅管理 MyCache 類的實例。

清單 24. MyCache
package cacheOfAnno; import java.util.HashMap; import java.util.Map; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; public class MyCache implements Cache { private String name; private Map<String,Account> store = new HashMap<String,Account>();; public MyCache() { } public MyCache(String name) { this.name = name; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Object getNativeCache() { return store; } @Override public ValueWrapper get(Object key) { ValueWrapper result = null; Account thevalue = store.get(key); if(thevalue!=null) { thevalue.setPassword("from mycache:"+name); result = new SimpleValueWrapper(thevalue); } return result; } @Override public void put(Object key, Object value) { Account thevalue = (Account)value; store.put((String)key, thevalue); } @Override public void evict(Object key) { } @Override public void clear() { } }

上面的自定義緩存只實現(xiàn)了很簡單的邏輯,但這是我們自己做的,也很令人激動是不是,主要看 get 和 put 方法,其中的 get 方法留了一個后門,即所有的從緩存查詢返回的對象都將其 password 字段設(shè)置為一個特殊的值,這樣我們等下就能演示“我們的緩存確實在起作用!”了。

這還不夠,spring 還不知道我們寫了這些東西,需要通過 spring*.xml 配置文件告訴它

清單 25. Spring-cache-anno.xml
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:cache="http://www.springframework.org/schema/cache"xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> <bean id="accountServiceBean" class="cacheOfAnno.AccountService"/> <!-- generic cache manager --> <bean id="cacheManager" class="cacheOfAnno.MyCacheManager"><property name="caches"> <set> <bean class="cacheOfAnno.MyCache"p:name="accountCache" /> </set> </property> </bean> </beans>

注意上面配置文件的黑體字,這些配置說明了我們的 cacheManager 和我們自己的 cache 實例。

好,什么都不說,測試!

清單 26. Main.java
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); Account account = s.getAccountByName("someone"); System.out.println("passwd="+account.getPassword()); account = s.getAccountByName("someone"); System.out.println("passwd="+account.getPassword()); }

上面的測試代碼主要是先調(diào)用 getAccountByName 進(jìn)行一次查詢,這會調(diào)用數(shù)據(jù)庫查詢,然后緩存到 mycache 中,然后我打印密碼,應(yīng)該是空的;下面我再次查詢 someone 的賬號,這個時候會從 mycache 中返回緩存的實例,記得上面的后門么?我們修改了密碼,所以這個時候打印的密碼應(yīng)該是一個特殊的值

清單 27. 運行結(jié)果
real querying db...someone passwd=null passwd=from mycache:accountCache

結(jié)果符合預(yù)期,即第一次查詢數(shù)據(jù)庫,且密碼為空,第二次打印了一個特殊的密碼。說明我們的 myCache 起作用了。

注意和限制

基于 proxy 的 spring aop 帶來的內(nèi)部調(diào)用問題

上面介紹過 spring cache 的原理,即它是基于動態(tài)生成的 proxy 代理機(jī)制來對方法的調(diào)用進(jìn)行切面,這里關(guān)鍵點是對象的引用問題,如果對象的方法是內(nèi)部調(diào)用(即 this 引用)而不是外部引用,則會導(dǎo)致 proxy 失效,那么我們的切面就失效,也就是說上面定義的各種注釋包括 @Cacheable、@CachePut 和 @CacheEvict 都會失效,我們來演示一下。

清單 28. AccountService.java
public Account getAccountByName2(String userName) { return this.getAccountByName(userName); } @Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCache public Account getAccountByName(String userName) { // 方法內(nèi)部實現(xiàn)不考慮緩存邏輯,直接實現(xiàn)業(yè)務(wù)return getFromDB(userName); }

上面我們定義了一個新的方法 getAccountByName2,其自身調(diào)用了 getAccountByName 方法,這個時候,發(fā)生的是內(nèi)部調(diào)用(this),所以沒有走 proxy,導(dǎo)致 spring cache 失效

清單 29. Main.java
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); s.getAccountByName2("someone"); s.getAccountByName2("someone"); s.getAccountByName2("someone"); }
清單 30. 運行結(jié)果
real querying db...someone real querying db...someone real querying db...someone

可見,結(jié)果是每次都查詢數(shù)據(jù)庫,緩存沒起作用。要避免這個問題,就是要避免對緩存方法的內(nèi)部調(diào)用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式來解決這個問題。

@CacheEvict 的可靠性問題

我們看到,@CacheEvict 注釋有一個屬性 beforeInvocation,缺省為 false,即缺省情況下,都是在實際的方法執(zhí)行完成后,才對緩存進(jìn)行清空操作。期間如果執(zhí)行方法出現(xiàn)異常,則會導(dǎo)致緩存清空不被執(zhí)行。我們演示一下

清單 31. AccountService.java
@CacheEvict(value="accountCache",allEntries=true)// 清空 accountCache 緩存public void reload() { throw new RuntimeException(); }

注意上面的代碼,我們在 reload 的時候拋出了運行期異常,這會導(dǎo)致清空緩存失敗。

清單 32. Main.java
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "spring-cache-anno.xml");// 加載 spring 配置文件AccountService s = (AccountService) context.getBean("accountServiceBean"); s.getAccountByName("someone"); s.getAccountByName("someone"); try { s.reload(); } catch (Exception e) { } s.getAccountByName("someone"); }

上面的測試代碼先查詢了兩次,然后 reload,然后再查詢一次,結(jié)果應(yīng)該是只有第一次查詢走了數(shù)據(jù)庫,其他兩次查詢都從緩存,第三次也走緩存因為 reload 失敗了。

清單 33. 運行結(jié)果
real querying db...someone

和預(yù)期一樣。那么我們?nèi)绾伪苊膺@個問題呢?我們可以用 @CacheEvict 注釋提供的 beforeInvocation 屬性,將其設(shè)置為 true,這樣,在方法執(zhí)行前我們的緩存就被清空了。可以確保緩存被清空。

清單 34. AccountService.java
@CacheEvict(value="accountCache",allEntries=true,beforeInvocation=true)// 清空 accountCache 緩存public void reload() { throw new RuntimeException(); }

注意上面的代碼,我們在 @CacheEvict 注釋中加了 beforeInvocation 屬性,確保緩存被清空。

執(zhí)行相同的測試代碼

清單 35. 運行結(jié)果
real querying db...someone real querying db...someone

這樣,第一次和第三次都從數(shù)據(jù)庫取數(shù)據(jù)了,緩存清空有效。

非 public 方法問題

和內(nèi)部調(diào)用問題類似,非 public 方法如果想實現(xiàn)基于注釋的緩存,必須采用基于 AspectJ 的 AOP 機(jī)制,這里限于篇幅不再細(xì)述。

其他技巧

Dummy CacheManager 的配置和作用

有的時候,我們在代碼遷移、調(diào)試或者部署的時候,恰好沒有 cache 容器,比如 memcache 還不具備條件,h2db 還沒有裝好等,如果這個時候你想調(diào)試代碼,豈不是要瘋掉?這里有一個辦法,在不具備緩存條件的時候,在不改代碼的情況下,禁用緩存。

方法就是修改 spring*.xml 配置文件,設(shè)置一個找不到緩存就不做任何操作的標(biāo)志位,如下

清單 36. Spring-cache-anno.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:cache="http://www.springframework.org/schema/cache"xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> <bean id="accountServiceBean" class="cacheOfAnno.AccountService"/> <!-- generic cache manager --> <bean id="simpleCacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="default" /> </set> </property> </bean> <!-- dummy cacheManager --> <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager"><property name="cacheManagers"> <list> <ref bean="simpleCacheManager" /> </list> </property> <property name="fallbackToNoOpCache" value="true" /> </bean> </beans>

注意以前的 cacheManager 變?yōu)榱?simpleCacheManager,且沒有配置 accountCache 實例,后面的 cacheManager 的實例是一個 CompositeCacheManager,他利用了前面的 simpleCacheManager 進(jìn)行查詢,如果查詢不到,則根據(jù)標(biāo)志位 fallbackToNoOpCache 來判斷是否不做任何緩存操作。

清單 37. 運行結(jié)果
real querying db...someone real querying db...someone real querying db...someone

可以看出,緩存失效。每次都查詢數(shù)據(jù)庫。因為我們沒有配置它需要的 accountCache 實例。

如果將上面 xml 配置文件的 fallbackToNoOpCache 設(shè)置為 false,再次運行,則會得到

清單 38. 運行結(jié)果
Exception in thread "main" java.lang.IllegalArgumentException: Cannot find cache named [accountCache] for CacheableOperation [public cacheOfAnno.Account cacheOfAnno.AccountService.getAccountByName(java.lang.String)]caches=[accountCache] | condition='' | key=''

可見,在找不到 accountCache,且沒有將 fallbackToNoOpCache 設(shè)置為 true 的情況下,系統(tǒng)會拋出異常。

小結(jié)

總之,注釋驅(qū)動的 spring cache 能夠極大的減少我們編寫常見緩存的代碼量,通過少量的注釋標(biāo)簽和配置文件,即可達(dá)到使代碼具備緩存的能力。且具備很好的靈活性和擴(kuò)展性。但是我們也應(yīng)該看到,spring cache 由于急于 spring AOP 技術(shù),尤其是動態(tài)的 proxy 技術(shù),導(dǎo)致其不能很好的支持方法的內(nèi)部調(diào)用或者非 public 方法的緩存設(shè)置,當(dāng)然這都是可以解決的問題,通過學(xué)習(xí)這個技術(shù),我們能夠認(rèn)識到,AOP 技術(shù)的應(yīng)用還是很廣泛的,如果有興趣,我相信你也能基于 AOP 實現(xiàn)自己的緩存方案。

原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/

轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/4231202.html

總結(jié)

以上是生活随笔為你收集整理的注释驱动的 Spring cache 缓存介绍--转载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

国产做aⅴ在线视频播放 | 欧美一级片在线播放 | 久久免费一 | 中文字幕有码在线播放 | 国产剧在线观看片 | 丁香六月激情婷婷 | www.黄色网.com| 日韩免费区 | 国产视频精品网 | 中文字幕在线观看免费 | 国产免费二区 | 欧美日韩亚洲国产一区 | 色的网站在线观看 | 国产成人av电影在线观看 | 奇米影视8888| 91色偷偷| 国产精品一区二区三区免费看 | 欧洲一区二区在线观看 | 国产麻豆剧果冻传媒视频播放量 | 亚洲视频电影在线 | 亚洲精品中文在线观看 | 黄a网站 | 久久久91精品国产一区二区三区 | 中文字幕免费国产精品 | www.啪啪.com| 免费网站黄色 | 久久看视频 | 最新91在线视频 | 手机av片| 亚洲成人黄色在线观看 | 国产精品一区二区三区四区在线观看 | 天天躁日日躁狠狠躁av麻豆 | 96久久欧美麻豆网站 | 中文字幕av在线播放 | 亚洲综合射 | 日韩系列在线观看 | 激情五月婷婷丁香 | 国产一区二区高清不卡 | 亚洲国产97在线精品一区 | 蜜臀av麻豆 | 最近高清中文字幕 | 日韩免费电影一区二区三区 | 久久理论视频 | 中文字幕韩在线第一页 | 欧美另类xxx| 国产原创av在线 | 精品亚洲免费视频 | 国产精品一区二区在线观看 | 99久久婷婷国产 | 成人在线黄色电影 | 97精品国产一二三产区 | 亚洲一区二区三区四区在线视频 | 911国产在线观看 | 国产一区视频免费在线观看 | 亚洲三级在线播放 | 免费在线观看不卡av | 亚洲免费在线播放视频 | 亚洲精品网站 | 国产999精品久久久影片官网 | 又粗又长又大又爽又黄少妇毛片 | 国产精品99免视看9 国产精品毛片一区视频 | 伊人看片| 久久艹国产 | 成人免费视频视频在线观看 免费 | 日韩a级黄色 | 97超碰免费在线 | 国产精品亚洲视频 | 欧美激情精品久久久久久免费 | 国产一级二级三级视频 | 欧美在线aa | 麻豆视传媒官网免费观看 | 久久久久久久久久久久久国产精品 | 欧美一级片免费观看 | 黄色网中文字幕 | 亚洲电影第一页av | 天天色天天射天天操 | 99在线视频精品 | 久久久色 | 亚洲精品免费观看 | 成年人视频在线观看免费 | 日韩a在线 | 日韩高清免费无专码区 | 91精品视频导航 | 四虎影视8848dvd | 狠狠地日| 91精品秘密在线观看 | 久久久久久久久久伊人 | 亚洲国产精久久久久久久 | 久久中国精品 | 成人av免费看 | 波多野结衣亚洲一区二区 | 亚洲欧美视频一区二区三区 | 91精品办公室少妇高潮对白 | 99久久精品国产网站 | 亚洲午夜剧场 | 精品一区二区免费视频 | 国内精品在线一区 | 免费看的黄色的网站 | 国产在线精品福利 | 国产五月 | 蜜臀精品久久久久久蜜臀 | 欧美激情另类文学 | 国产1级视频 | 国产淫片| 日韩精品免费在线观看视频 | 最新午夜| 久久成人一区二区 | 久久精品日产第一区二区三区乱码 | 色视频网站免费观看 | 久久99免费视频 | 久久午夜鲁丝片 | 毛片的网址 | 最新日韩电影 | 91av在线免费播放 | av成人动漫在线观看 | 日韩精品视频网站 | 国产一二区视频 | 久久99久久99精品免视看婷婷 | 国产精品一区二区吃奶在线观看 | 久久精选 | 精品一区二区免费在线观看 | 久久久国产视频 | 国产日韩在线看 | 色综合久久久久综合 | 国产免费三级在线观看 | 久久视频免费 | 五月天av在线 | 精品视频中文字幕 | 午夜黄色影院 | 18做爰免费视频网站 | 中国精品一区二区 | 亚洲 欧美 91 | 丁香在线观看完整电影视频 | 欧美一级视频一区 | 丝袜护士aⅴ在线白丝护士 天天综合精品 | 欧美午夜性生活 | 国产成人综合在线观看 | 日韩综合在线观看 | 国产日韩欧美视频在线观看 | 精品国产成人av在线免 | 国产视频一区二区在线 | 免费看黄的视频 | 日韩在线第一区 | 99久久精品一区二区成人 | 一区 二区电影免费在线观看 | www.久久色.com | 欧美91成人网 | 精品免费一区二区三区 | av东方在线 | 国产精品青草综合久久久久99 | 国产美女精彩久久 | 菠萝菠萝蜜在线播放 | 欧美analxxxx| 国产1级毛片 | 久久久久久福利 | av大片免费在线观看 | 久久欧美在线电影 | 亚洲精品五月 | 在线免费观看黄色大片 | 天天操天天操一操 | 免费毛片一区二区三区久久久 | 欧美日韩电影在线播放 | 夜夜操天天| 欧美影院久久 | 91精品国产一区 | 久久午夜网 | 久久精品99国产精品亚洲最刺激 | 久久99精品久久久久婷婷 | 91麻豆精品国产91久久久久 | 精品国产亚洲在线 | 精品国产人成亚洲区 | 黄在线免费观看 | 美女网站色免费 | 夜夜躁狠狠燥 | 亚洲精品乱码久久久久久高潮 | 欧美精品一区二区免费 | 亚洲综合狠狠干 | 久久一精品 | 狠狠狠色狠狠色综合 | 久久免费精彩视频 | 国产精品完整版 | 射射射av| 国产精品毛片一区二区在线 | 亚洲成人av在线电影 | 在线观看视频色 | 91视频 - v11av| 久久久午夜视频 | 中文字幕在线播放av | 碰超在线97人人 | 狠狠操导航 | 欧美五月婷婷 | 日韩免费播放 | 日韩高清免费在线 | 久草在线视频首页 | 久久综合久久综合九色 | 五月婷婷综合在线 | 国产精品九九热 | 久久精品—区二区三区 | 97在线视频免费看 | 欧美日韩中文国产一区发布 | 国产小视频免费在线观看 | 久久综合久久久久88 | 日韩欧美一区二区三区视频 | 日韩大片免费在线观看 | 国产成人精品亚洲日本在线观看 | 国产在线一区二区 | 欧美少妇xxxxxx | 日韩在线视频国产 | 欧美在线视频第一页 | 欧美精品久久久久久久久久白贞 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 91大神精品视频 | 韩国三级在线一区 | 国产999视频在线观看 | 欧美一级视频一区 | 天天操天天操天天操天天操天天操天天操 | 亚洲黄色免费在线 | 九七视频在线观看 | 最新中文字幕在线播放 | 国产一区二区高清不卡 | 98精品国产自产在线观看 | 一级黄色大片在线观看 | 51精品国自产在线 | 美女福利视频 | 99久久精品免费看国产麻豆 | 狠狠干天天射 | 亚洲一区尤物 | 中文字幕中文中文字幕 | 欧美乱码精品一区二区 | 香蕉久久久久久久 | 国产一级免费观看 | 99色人| 黄色三级免费观看 | 国产字幕在线播放 | 免费看一级特黄a大片 | 久久a久久 | 久久a v视频| 中文字幕a在线 | 99热在线看 | 麻豆91在线 | 日日夜夜草 | 一级片视频免费观看 | 超碰97在线资源站 | 国产麻豆成人传媒免费观看 | 在线视频久久 | 日韩免费电影在线观看 | 国产精品一区二区三区久久久 | 国产真实精品久久二三区 | 九色琪琪久久综合网天天 | 国产一区欧美在线 | 免费久久99精品国产 | 色偷偷88888欧美精品久久 | 久久综合中文色婷婷 | 精选久久 | 91av福利视频 | 国产欧美高清 | 精品专区一区二区 | 在线看成人 | 成人小视频在线观看免费 | 91在线视频 | 黄色影院在线免费观看 | 最近中文字幕大全 | 久久全国免费视频 | 日韩欧美电影在线观看 | 天天曰夜夜操 | 91福利小视频 | 久久久久久蜜桃一区二区 | 五月天丁香 | 中文字幕乱在线伦视频中文字幕乱码在线 | 国精产品永久999 | 黄色的视频| 欧美日韩精品在线免费观看 | 国产精品va在线观看入 | 天堂网一区 | 日韩有码网站 | 久久一视频 | 欧洲精品码一区二区三区免费看 | 国产日韩欧美在线免费观看 | 国产高清久久久 | 视频一区二区精品 | 久久综合导航 | 久久综合电影 | 天天操月月操 | 97高清视频 | 粉嫩高清一区二区三区 | 天堂在线一区二区三区 | 国产一区二区久久久 | 成人a在线观看高清电影 | 免费网站观看www在线观看 | 在线观看福利网站 | 最近高清中文在线字幕在线观看 | 最新av在线免费观看 | 国产精品mv在线观看 | 亚洲国产999 | 国产精品永久久久久久久久久 | 精品99在线观看 | 免费在线观看av | 免费看毛片网站 | 黄污网| 91亚洲永久精品 | 99综合电影在线视频 | 亚洲国产操 | 国产99自拍 | 国产精品男女啪啪 | 日韩a级免费视频 | 亚洲 欧美 成人 | av资源在线观看 | 久久久免费观看视频 | 超碰官网 | 草莓视频在线观看免费观看 | 四虎影视精品成人 | 在线之家免费在线观看电影 | 国产精品久久久久久影院 | 超碰人人超 | 91免费版成人 | 中文字幕91 | 国产精品久久久久久久久久久免费 | 亚洲激情免费 | 四虎国产精品免费 | 激情av资源网| 久久精品久久久久久久 | 最近更新中文字幕 | 亚洲精品字幕在线观看 | 黄色软件网站在线观看 | 国产理论片在线观看 | 久久久久久久久久福利 | 人人爽人人爽人人片 | 国产经典三级 | 国产中文字幕一区 | 中文字字幕在线 | 深夜精品福利 | av电影在线观看完整版一区二区 | 久久精品一二区 | 在线视频 你懂得 | 97人人射| 日日操网 | 国产精品久久久久久一区二区三区 | 国产精品久久久一区二区三区网站 | www.久草视频 | 婷婷丁香在线观看 | 91视频大全| 国产又粗又硬又爽的视频 | 亚洲精品中文字幕在线观看 | 天天爱综合 | 在线a人v观看视频 | 成年人在线免费看片 | 正在播放五月婷婷狠狠干 | 久久福利小视频 | 日本护士三级少妇三级999 | 日本视频久久久 | 1024手机在线看 | 国产一区视频在线观看免费 | 日日干美女 | 超碰97人人在线 | 日韩欧美视频在线观看免费 | 国产黄色视 | 最近日韩免费视频 | 欧美精品久久久 | 91.麻豆视频 | 欧洲色吧 | 99精品小视频 | 亚洲精品五月 | 少妇性bbb搡bbb爽爽爽欧美 | 狠狠综合 | 免费高清在线视频一区· | 国产精品 日韩 | 日日干网| 成人黄色电影在线 | 国产日韩精品在线观看 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 国产中文字幕在线观看 | 日韩久久片 | 精品九九九九 | 国产精品久久久电影 | 精品久久久99 | 欧美在线一二区 | 亚洲成a人片77777潘金莲 | 免费亚洲一区二区 | 一区二区三区免费 | 免费黄色av | 日韩成人免费在线 | 色噜噜在线观看视频 | 深夜福利视频一区二区 | 国产精品久久久久久久久久东京 | 网址你懂的在线观看 | 亚洲高清久久久 | 最新中文字幕在线资源 | 黄色在线观看www | 久久免费黄色 | 91系列在线观看 | 日本黄区免费视频观看 | 国产免费xvideos视频入口 | 香蕉色综合| 久久久国产精品视频 | 天天草天天干天天 | 国产精品精 | 国产精品一区免费观看 | 中文字幕在线观看资源 | 久久久久久久免费 | 91成年人在线观看 | 综合天天 | 国产成人精品亚洲 | 激情五月婷婷丁香 | 一本之道乱码区 | 亚洲aⅴ在线观看 | 久久免费福利 | 天天操天天操天天 | 色网站中文字幕 | 九九九免费视频 | 三级毛片视频 | 99视频+国产日韩欧美 | 又黄又爽又湿又无遮挡的在线视频 | 久草在线综合网 | 国产手机免费视频 | 国产剧情在线一区 | 五月综合婷 | 天天色天天色 | 亚洲狠狠丁香婷婷综合久久久 | a在线免费观看视频 | 国产午夜麻豆影院在线观看 | 精品一二三四视频 | 在线a视频免费观看 | 日本精品一区二区在线观看 | 999久久国产精品免费观看网站 | 天天爱av导航 | 日韩v欧美v日本v亚洲v国产v | 久草久视频 | 999亚洲国产996395 | 欧美日韩性视频在线 | 亚洲激情校园春色 | 日韩高清网站 | 国色天香第二季 | 国产精品一区二区三区四区在线观看 | 奇米网在线观看 | 久久人人爽人人爽人人片av软件 | av免费观看高清 | 亚洲国产精久久久久久久 | 色综合网在线 | 99免费在线视频观看 | 免费精品视频在线观看 | 亚洲精品在线免费看 | 91麻豆精品国产91久久久久久久久 | 亚洲成人av在线电影 | 中文不卡视频在线 | 亚洲开心激情 | 精品久久精品久久 | 成人午夜黄色影院 | 欧美在线不卡一区 | 欧美男男激情videos | 在线欧美a | 亚洲视频一 | 久久精品一区二区三区国产主播 | 亚洲天天干 | 免费av一级电影 | 中文字幕视频一区二区 | 精品一区三区 | 一区二区三区在线不卡 | 日韩成人精品 | 男女拍拍免费视频 | 久久亚洲私人国产精品 | 国产视频在 | 欧美一级小视频 | 日韩精品视频网站 | 日韩毛片精品 | 九九免费在线观看 | 天堂av免费看 | 手机成人av在线 | 黄色特级一级片 | 99热在线免费观看 | 中文字幕一区二区三区在线观看 | 97看片吧| 麻豆影视网 | 九九视频一区 | 亚洲欧美视频一区二区三区 | 久久久久久欧美二区电影网 | 黄色最新网址 | 国产一级视频 | 国产拍揄自揄精品视频麻豆 | 爱爱av网 | 91精品小视频 | 欧美伦理电影一区二区 | 欧美一二区视频 | 91久久久久久久一区二区 | 黄色在线观看免费网站 | 日韩一区二区三免费高清在线观看 | 99这里有精品 | 99精品在线免费视频 | 中文字幕免费在线看 | 国产精品视频区 | 国内精品视频一区二区三区八戒 | 亚洲精品美女久久久久网站 | 国产成视频在线观看 | 欧美精品午夜 | 天天干天天拍天天操天天拍 | 国产精品久久久av | 天天躁天天躁天天躁婷 | 国产一区二区在线免费观看 | 91精品国产麻豆国产自产影视 | 日韩精品免费一线在线观看 | 久久精品网站视频 | 一区二区三区精品久久久 | 国产精品久久久一区二区 | 成人资源在线 | 亚洲美女久久 | 日韩精品一区二区三区在线播放 | 久久香蕉电影网 | 天天做天天爱天天综合网 | 亚洲一区二区观看 | 日韩国产精品毛片 | 91在线影院| 国内精品中文字幕 | 五月天电影免费在线观看一区 | www.成人sex| 国产综合视频在线观看 | 久久嗨| 日本中文乱码卡一卡二新区 | av在线成人| 曰韩在线 | 国产色女 | 日本中文在线观看 | 精品国产一区二区三区久久久蜜臀 | 日韩a免费 | 五月婷婷综| 99久久er热在这里只有精品66 | 精品久久网| 在线 日韩 av| 在线一二三四区 | 二区三区精品 | 色婷婷狠狠18 | 精品久久久久免费极品大片 | 欧美午夜精品久久久久久孕妇 | 97高清免费视频 | 福利一区二区三区四区 | 国产第一二区 | 开心综合网| 国内毛片毛片 | 国产日产精品久久久久快鸭 | 久久av一区二区三区亚洲 | 91av在线免费 | 久久久久一区二区三区 | 丁香激情五月婷婷 | 久久夜色精品国产欧美一区麻豆 | 久久免费的精品国产v∧ | 久保带人 | 高清av免费看 | 成人在线视 | 91在线网站 | 欧洲色吧| 色av网站 | 久久午夜国产 | 伊人黄 | 色视频网站在线观看一=区 a视频免费在线观看 | 婷婷激情综合 | 婷婷电影在线观看 | 婷色在线 | 国产剧情一区二区在线观看 | 免费在线中文字幕 | 丁香婷婷综合色啪 | 欧美在线观看小视频 | 9草在线| 亚洲高清av在线 | 国产精品女人网站 | 欧美精品亚洲二区 | 欧美精品乱码久久久久久 | 精品久久久久亚洲 | 国产尤物一区二区三区 | 一区二区视频免费在线观看 | 人人爽人人爽人人片 | 国产精品原创视频 | 97av在线视频 | 五月天激情综合网 | 免费视频久久久久 | 99人久久精品视频最新地址 | 欧美日韩综合在线观看 | 永久黄网站色视频免费观看w | 激情av一区二区 | 国产在线观看91 | 97超碰国产精品女人人人爽 | 成人av教育 | 久久精品视频免费播放 | 日韩资源在线观看 | 精品免费视频. | 国产精品久久久久久69 | 97超碰中文 | 在线观看黄色国产 | 特级黄色电影 | 97人人模人人爽人人少妇 | 国产精品九九热 | 麻豆91在线看 | 国产高清免费视频 | 狠狠操影视| 久操久| 亚洲精品国偷自产在线91正片 | 日本在线观看一区二区三区 | 国产精品毛片久久久 | 精品在线一区二区 | 看毛片的网址 | 99久久精品免费看国产免费软件 | 中文字幕在线网 | 日韩精品aaa | 97视频一区 | 国产原创在线 | 亚洲国产一区av | 日韩av电影网站在线观看 | 国产香蕉视频在线播放 | 亚洲另类视频 | 日本精品久久 | 日韩免费成人 | 亚洲最大激情中文字幕 | 国产日产精品久久久久快鸭 | 97超视频免费观看 | 国产自偷自拍 | 国产永久免费高清在线观看视频 | 精品久久91 | 99久久精品国产观看 | 一区二区三区在线播放 | 在线精品亚洲一区二区 | 国产精品高潮呻吟久久久久 | bbbb操bbbb| 成人国产在线 | 在线观看国产v片 | 久久艹中文字幕 | 在线观看黄 | 最新av免费 | 国产第一福利网 | 在线视频观看国产 | 国产在线a免费观看 | 福利一区在线 | 亚洲精品乱码白浆高清久久久久久 | 久草精品在线观看 | 免费情趣视频 | 色婷婷视频在线 | 美女久久一区 | 亚洲欧洲精品一区 | 国产成人精品久久亚洲高清不卡 | 天无日天天操天天干 | 亚洲黄色av | 成人在线黄色 | av在线专区| 久久久精品小视频 | 色婷婷成人网 | 在线观看成人av | 欧美色伊人 | 黄污在线观看 | 久久线视频 | 成年人视频在线免费观看 | 深夜免费福利视频 | 美女视频黄的免费的 | 在线观看的av | 99久久综合精品五月天 | 久久午夜精品 | 又黄又爽的免费高潮视频 | 国产成人精品国内自产拍免费看 | 免费影视大全推荐 | 国产精品成人在线观看 | av短片在线| 亚洲天堂激情 | 国产精品毛片久久久久久 | 免费一级片久久 | 日韩av一卡二卡三卡 | 免费在线色 | 精品日本视频 | 99久久久国产精品免费99 | 中文字幕 在线 一 二 | 久久这里只有精品23 | 精品国产一区二区三区久久久蜜月 | 玖玖玖国产精品 | 国产视频精选在线 | 91久久偷偷做嫩草影院 | 久久久蜜桃| 亚洲丁香日韩 | 亚洲最大av在线播放 | 日韩免费视频线观看 | 日韩精品中文字幕在线观看 | 久久免费电影网 | 91精彩视频 | 玖玖精品在线 | 久久免费视频观看 | 黄色大片国产 | 九九久久婷婷 | 国产精品毛片一区二区在线 | 麻花天美星空视频 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 国产99久久久国产精品免费看 | www国产亚洲 | 一区二区影院 | 精品国产精品久久 | 人人爽人人澡 | 五月天狠狠操 | 成年人视频在线免费观看 | 国产午夜精品福利视频 | 国产在线va| 九九热国产视频 | 欧美视频不卡 | 久草在线视频在线 | 久草资源在线观看 | 日韩av看片 | 精品久久在线 | 99久免费精品视频在线观看 | 久久久福利 | 色视频成人在线观看免 | 午夜少妇av | 91av久久 | 国产在线超碰 | 成人精品一区二区三区电影免费 | 波多野结衣久久精品 | 成人av在线直播 | 国产中文字幕av | 欧美天天综合网 | 超碰日韩在线 | 国产无遮挡又黄又爽在线观看 | 不卡的av电影 | 日韩在线视频看看 | 久久久久欧美精品999 | 成人免费共享视频 | www.天天成人国产电影 | 狠狠亚洲| 日韩欧美电影在线 | 美女视频黄网站 | 成人国产一区 | 日韩毛片精品 | 精品国产一区二区三区四 | 综合中文字幕 | 国产馆在线播放 | 伊人va | 久久99亚洲网美利坚合众国 | 91污视频在线| 激情网五月婷婷 | 国产精品96久久久久久吹潮 | 久久精品视频网址 | 中文字幕在线看视频 | 日日躁你夜夜躁你av蜜 | 91看片看淫黄大片 | 久久综合免费视频影院 | 高潮毛片无遮挡高清免费 | 天天爱天天操天天爽 | 色夜视频 | 国产淫片免费看 | 99看视频在线观看 | 91爱看片| 久久久久久蜜av免费网站 | 黄污污网站 | 亚洲理论电影网 | 国产一区麻豆 | 日韩av成人免费看 | 精品国产一区二区三区四区在线观看 | 国产精选视频 | 视频国产一区二区三区 | 久久精品国产v日韩v亚洲 | 国产麻豆精品久久一二三 | 亚洲成人频道 | 97人人精品 | bbb搡bbb爽爽爽| 中文字幕在线观看完整版电影 | 中文字幕在线观看完整 | 久久一久久 | 麻豆一级视频 | 黄视频网站大全 | 91天堂影院 | 91精品国自产在线 | 在线播放亚洲 | 日躁夜躁狠狠躁2001 | 一本一道波多野毛片中文在线 | 日韩在线观看电影 | 在线精品视频免费观看 | 天天干天天操天天做 | 91最新在线观看 | 久久久久国产精品午夜一区 | 日韩av片免费在线观看 | 国产精品综合久久 | 偷拍福利视频一区二区三区 | 三级大片网站 | 精品国产一区二区久久 | 精品福利在线视频 | 欧美日韩国产二区三区 | 免费的黄色av | 女人高潮特级毛片 | 中文字幕色在线视频 | 国产在线欧美在线 | 久久国产免费看 | 国产精品 视频 | 亚洲少妇天堂 | 亚洲精品日韩一区二区电影 | 五月天天天操 | 欧美福利视频一区 | 欧美污在线观看 | 久久99这里只有精品 | 91色欧美| 91在线亚洲 | 一本一道波多野毛片中文在线 | 久久久久成人免费 | 国产乱码精品一区二区蜜臀 | 俺要去色综合狠狠 | 手机看片国产日韩 | 88av色 | 国产成人l区 | 91视频麻豆视频 | 国产精品午夜在线 | 国产一二三四在线视频 | 免费高清在线观看成人 | 97av色| 日精品 | 成人动态视频 | 蜜臀av网站 | 精品亚洲一区二区 | 精品在线免费视频 | 一区二区三区动漫 | 久久五月婷婷丁香 | 亚洲婷婷综合色高清在线 | av电影在线播放 | 国产精品久久久 | 中文字幕 影院 | 日本在线视频一区二区三区 | 激情 婷婷| 永久免费毛片在线观看 | 亚洲国产一区二区精品专区 | 日韩欧美一二三 | 亚洲午夜精品一区 | 精品一区精品二区 | 成人福利在线 | 色播五月激情综合网 | 一区二区三区四区五区在线 | 99精品视频免费观看 | 91传媒视频在线观看 | 在线观看免费 | 亚洲精品中文在线资源 | 一区二区三区免费在线观看视频 | 国产69久久久欧美一级 | 一级精品视频在线观看宜春院 | 国产系列精品av | 久久人人做 | 国产手机av在线 | 国产精品久久在线 | 在线精品观看国产 | 久久精品一区八戒影视 | 最新色站 | 亚洲第一成网站 | www.狠狠操.com | 激情五月色播五月 | 日日夜夜噜 | 18女毛片 | 亚洲精品动漫在线 | 五月婷婷开心中文字幕 | av视屏在线播放 | 黄色91免费观看 | 免费黄色激情视频 | 亚洲 欧美 91 | av免费黄色 | 人人玩人人添人人澡超碰 | 日韩精品中文字幕久久臀 | 国产精品久久久久久久久久免费 | 国产99久久九九精品 | 在线岛国av | 99在线免费视频观看 | 在线观看不卡视频 | 色偷偷网站视频 | 亚洲精品视频第一页 | 丁香五月网久久综合 | 在线中文字幕电影 | 国产色网站 | 99国产精品视频免费观看一公开 | 国产精品久久久久久久久免费看 | 亚洲综合一区二区精品导航 | 日韩精品一区二区不卡 | 国内精品视频免费 | 国产午夜精品视频 | 91麻豆精品国产91久久久使用方法 | 精品久久久久久国产偷窥 | 欧美成年性 | 精品久久久久久久久久久院品网 | 操操操影院 | 美女久久网站 | 久久免费公开视频 | 黄在线免费观看 | 91成人免费观看视频 | 免费成人在线视频网站 | 久久久久久久亚洲精品 | 中文字幕91| 麻豆视频网址 | 国产91精品在线观看 | 激情av网 | 在线亚洲欧美视频 | 伊人婷婷色 | 欧美一区二区三区在线播放 | 在线观看免费高清视频大全追剧 | 日韩免费高清在线 | 日日精品| 欧美成人高清 | 天堂av在线网址 | 久久,天天综合 | 日韩一级理论片 | 99久久免费看 | 456成人精品影院 | 日韩亚洲国产精品 | 亚洲国产精品成人综合 | 婷婷色资源 | 国产精品粉嫩 | 国内小视频在线观看 | 久草在线播放视频 | 成人啊 v | 一级片免费观看 | 日韩在线观看你懂得 | 国产久视频 | 色偷偷男人的天堂av | 亚洲第一av在线播放 | 亚洲欧美精品一区 | 国产精品自在线拍国产 | 中文字幕在线看片 | 国产精品一区二区白浆 | 毛片在线播放网址 | 国产黄色片免费 | 免费av片在线 | 六月激情婷婷 | 国产成人精品一区二区三区免费 | 久久视频在线免费观看 | 中国老女人日b | 91人人网| 美女久久久久 | 久久无码av一区二区三区电影网 | 人人玩人人弄 | 成人91视频| 在线观看免费高清视频大全追剧 | 国产偷在线 | 亚洲黄色av网址 | 国产精彩视频一区 | 干干操操| 69精品视频在线观看 | 久久综合久久久 | 999久久精品 | 国产一区欧美一区 | 日本精品在线视频 | 97精品国产97久久久久久久久久久久 | 亚洲视频电影在线 | 日韩性xxxx | 99久久精品日本一区二区免费 | 久久伦理网 | 国产精品一区二区美女视频免费看 | 天天天综合| 人人狠狠综合久久亚洲婷 | 国产伦理久久精品久久久久_ | 国产精品片 | 亚洲午夜精品电影 | 欧美另类色图 | 98涩涩国产露脸精品国产网 | 久久精品中文 | 亚洲欧洲精品在线 | 亚洲精品在线观看免费 | 日韩在线三区 | 日韩精品一区二区三区三炮视频 | 日韩精品免费一区二区三区 | 五月天精品视频 | 天天草视频 | 日本三级久久 | 日韩免费一级a毛片在线播放一级 | 在线免费观看国产精品 | 999久久精品 | 亚洲欧洲一区二区在线观看 | 天堂成人在线 | 日日夜夜天天人人 | 免费三级黄 | 国产精品久久久久av福利动漫 | 日本女人b | www.色午夜.com| 毛片激情永久免费 | 婷婷丁香六月天 | 国产精品视频免费 | 日韩三级视频在线观看 | 超级av在线 | 日韩理论在线观看 | 亚州性色 | 射九九 | 97偷拍在线视频 | 中文字幕在线视频免费播放 | 最新日韩在线观看视频 | 日韩精品久久久免费观看夜色 | 在线播放日韩av | 国产精品久久久久久久久软件 | 中文字幕二区在线观看 | 手机在线观看国产精品 | 99婷婷 | 五月激情丁香图片 | 欧美成人精品xxx | 久久久九色精品国产一区二区三区 | 成人9ⅰ免费影视网站 | 美女免费视频一区二区 | 美女网站在线看 | 国产精品av免费在线观看 | 91中文字幕网 | 欧洲精品在线视频 | 国产精品18久久久久vr手机版特色 | 波多野结衣在线观看一区 | 成年人在线观看免费视频 | 亚洲狠狠丁香婷婷综合久久久 | 人人舔人人爱 | 久久99久久99精品 | 久久久久99精品国产片 | 欧美日韩高清一区二区 国产亚洲免费看 | 久草视频在线观 |