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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

注解驱动的 Spring cache 缓存介绍

發布時間:2023/12/18 javascript 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 注解驱动的 Spring cache 缓存介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述

前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。

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

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

其特點總結如下:

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

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

原來我們是怎么做的

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

場景是:對一個賬號查詢方法做緩存,以賬號名稱為 key,賬號對象為 value,當以相同的賬號名稱查詢賬號的時候,直接從緩存中返回結果,否則更新緩存。賬號查詢服務還支持 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;} }
?

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

清單 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) {// 根據 key 來刪除緩存中的一條記錄if(cache.containsKey(key)) {cache.remove(key);}}public void evictCache() {// 清空緩存中的所有記錄cache.clear();} }

?

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

?

清單 3. MyAccountService.java

package oldcache;import cacheOfAnno.Account;public class MyAccountService {private MyCacheManager<Account> cacheManager;public MyAccountService() {cacheManager = new MyCacheManager<Account>();// 構造一個緩存管理器}public Account getAccountByName(String acctName) {Account result = cacheManager.getValue(acctName);// 首先查詢緩存if(result!=null) {System.out.println("get from cache..."+acctName);return result;// 如果在緩存中,則直接返回緩存的結果}result = getFromDB(acctName);// 否則到數據庫中查詢if(result!=null) {// 將數據庫查詢的結果更新到緩存中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);} }

?

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

?

清單 4. Main.java

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

?

按照分析,執行結果應該是:首先從數據庫查詢,然后直接返回緩存中的結果,重置緩存后,應該先從數據庫查詢,然后返回緩存中的結果,實際的執行結果如下:

?

清單 5. 運行結果

real querying db...somebody// 第一次從數據庫加載 get from cache...somebody// 第二次從緩存加載 after reload...// 清空緩存 real querying db...somebody// 又從數據庫加載 get from cache...somebody// 從緩存加載

?

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

?

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

如果你的代碼中有上述代碼的影子,那么你可以考慮按照下面的介紹來優化一下你的代碼結構了,也可以說是簡化,你會發現,你的代碼會變得優雅的多!

Hello World,注釋驅動的 Spring Cache

Hello World 的實現目標

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

需要的 jar 包

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

圖 1. 工程依賴的 jar 包圖

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

定義實體類、服務類和相關配置文件

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

清單 6. AccountService.java

package cacheOfAnno;import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable;public class AccountService {@Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCachepublic Account getAccountByName(String userName) {// 方法內部實現不考慮緩存邏輯,直接實現業務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”),這個注釋的意思是,當調用這個方法的時候,會從一個名叫 accountCache 的緩存中查詢,如果沒有,則執行實際的方法(即查詢數據庫),并將執行的結果存入緩存中,否則返回緩存中的對象。這里的緩存中的 key 就是參數 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/cachehttp://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><beanclass="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="default" /><beanclass="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="accountCache" /></set></property></bean> </beans>
?

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

OK,現在我們具備了測試條件,測試代碼如下:

清單 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");// 第一次查詢,應該走數據庫System.out.print("first query...");s.getAccountByName("somebody");// 第二次查詢,應該不查數據庫,直接返回緩存的值System.out.print("second query...");s.getAccountByName("somebody");System.out.println();} }

?

上面的測試代碼主要進行了兩次查詢,第一次應該會查詢數據庫,第二次應該返回緩存,不再查數據庫,我們執行一下,看看結果

?

清單 9. 執行結果

1 2 3 first query...real query account.somebody// 第一次查詢 real querying db...somebody// 對數據庫進行了查詢 second query...// 第二次查詢,沒有打印數據庫查詢日志,直接返回了緩存中的結果

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

如何清空緩存

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

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

  • 當外部調用更新了賬號,則我們需要更新此賬號對應的緩存
  • 當外部調用說明重新加載,則我們需要清空所有緩存

清單 10. AccountService.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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) { ????// 方法內部實現不考慮緩存邏輯,直接實現業務 ????return getFromDB(userName); ??} ??@CacheEvict(value="accountCache",key="#account.getName()")// 清空 accountCache 緩存? public void updateAccount(Account account) { ????updateDB(account); ??} ?? ??@CacheEvict(value="accountCache",allEntries=true)// 清空 accountCache 緩存 ??public void reload() { ??} ?? ??private Account getFromDB(String acctName) { ????System.out.println("real querying db..."+acctName); ????return new Account(acctName); ??} ?? ??private void updateDB(Account account) { ????System.out.println("real update db..."+account.getName()); ??} ?? }

清單 11. Main.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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"); ????// 第一次查詢,應該走數據庫 ????System.out.print("first query..."); ????s.getAccountByName("somebody"); ????// 第二次查詢,應該不查數據庫,直接返回緩存的值 ????System.out.print("second query..."); ????s.getAccountByName("somebody"); ????System.out.println(); ???? ????System.out.println("start testing clear cache...");??? // 更新某個記錄的緩存,首先構造兩個賬號記錄,然后記錄到緩存中 ????Account account1 = s.getAccountByName("somebody1"); ????Account account2 = s.getAccountByName("somebody2"); ????// 開始更新其中一個??? account1.setId(1212); ????s.updateAccount(account1); ????s.getAccountByName("somebody1");// 因為被更新了,所以會查詢數據庫??? s.getAccountByName("somebody2");// 沒有更新過,應該走緩存??? s.getAccountByName("somebody1");// 再次查詢,應該走緩存??? // 更新所有緩存 ????s.reload(); ????s.getAccountByName("somebody1");// 應該會查詢數據庫??? s.getAccountByName("somebody2");// 應該會查詢數據庫??? s.getAccountByName("somebody1");// 應該走緩存??? s.getAccountByName("somebody2");// 應該走緩存 ??} }

清單 12. 運行結果

1 2 3 4 5 6 7 8 9 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

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

如何按照條件操作緩存

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

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

清單 13. AccountService.java(getAccountByName 方法修訂,支持條件)

1 2 3 4 5 @Cacheable(value="accountCache",condition="#userName.length() <= 4")// 緩存名叫 accountCache public Account getAccountByName(String userName) { // 方法內部實現不考慮緩存邏輯,直接實現業務 return getFromDB(userName); }

注意其中的 condition=”#userName.length() <=4”,這里使用了 SpEL 表達式訪問了參數 userName 對象的 length() 方法,條件表達式返回一個布爾值,true/false,當條件為 true,則進行緩存操作,否則直接調用方法執行的返回結果。

清單 14. 測試方法

1 2 3 4 s.getAccountByName("somebody");// 長度大于 4,不會被緩存 s.getAccountByName("sbd");// 長度小于 4,會被緩存 s.getAccountByName("somebody");// 還是查詢數據庫 s.getAccountByName("sbd");// 會從緩存返回

清單 15. 運行結果

1 2 3 real querying db...somebody real querying db...sbd real querying db...somebody

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

如果有多個參數,如何進行 key 的組合

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

清單 16. Account.java(增加 password 屬性)

1 2 3 4 5 6 7 private String password; public String getPassword() { ??return password; } public void setPassword(String password) { ??this.password = password; }

清單 17. AccountService.java(增加 getAccount 方法,支持組合 key)

1 2 3 4 5 6 @Cacheable(value="accountCache",key="#userName.concat(#password)") public Account getAccount(String userName,String password,boolean sendLog) { ??// 方法內部實現不考慮緩存邏輯,直接實現業務 ??return getFromDB(userName,password); ?? }

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

清單 18. Main.java

1 2 3 4 5 6 7 8 9 10 11 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);// 應該查詢數據庫 ??s.getAccount("somebody", "123456", true);// 應該走緩存 ??s.getAccount("somebody", "123456", false);// 應該走緩存 ??s.getAccount("somebody", "654321", true);// 應該查詢數據庫 ??s.getAccount("somebody", "654321", true);// 應該走緩存 }

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

清單 19. 運行結果

1 2 real querying db...userName=somebody password=123456 real querying db...userName=somebody password=654321

和我們預期的一致。

如何做到:既要保證方法被調用,又希望結果被緩存

根據前面的例子,我們知道,如果使用了 @Cacheable 注釋,則當重復使用相同參數調用方法的時候,方法本身不會被調用執行,即方法本身被略過了,取而代之的是方法的結果直接從緩存中找到并返回了。

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

清單 20. AccountService.java

1 2 3 4 5 6 7 8 9 10 11 12 13 @Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCache public Account getAccountByName(String userName) { ??// 方法內部實現不考慮緩存邏輯,直接實現業務 ??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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 的賬號,這個時候會查詢數據庫一次,但是也記錄到緩存中了。然后我們修改了密碼,調用了 updateAccount 方法,這個時候會執行數據庫的更新操作且記錄到緩存,我們再次修改密碼并調用 updateAccount 方法,然后通過 getAccountByName 方法查詢,這個時候,由于緩存中已經有數據,所以不會查詢數據庫,而是直接返回最新的數據,所以打印的密碼應該是“321”

清單 22. 運行結果

1 2 3 4 real querying db...someone real updating db...someone real updating db...someone 321

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

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

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

表 1. @Cacheable 作用和配置方法

@Cacheable 的作用主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存
@Cacheable 主要的參數
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@Cacheable(value=”mycache”) 或者?
@Cacheable(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

表 2. @CachePut 作用和配置方法

@CachePut 的作用主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存,和 @Cacheable 不同的是,它每次都會觸發真實方法的調用
@CachePut 主要的參數
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@Cacheable(value=”mycache”) 或者?
@Cacheable(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合例如:
@Cacheable(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

表 3. @CacheEvict 作用和配置方法

@CachEvict 的作用主要針對方法配置,能夠根據一定的條件對緩存進行清空
@CacheEvict 主要的參數
value緩存的名稱,在 spring 配置文件中定義,必須指定至少一個例如:
@CachEvict(value=”mycache”) 或者?
@CachEvict(value={”cache1”,”cache2”}
key緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合例如:
@CachEvict(value=”testcache”,key=”#userName”)
condition緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才清空緩存例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
allEntries是否清空所有緩存內容,缺省為 false,如果指定為 true,則方法調用后將立即清空所有緩存例如:
@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation是否在方法執行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執行的時候就清空緩存,缺省情況下,如果方法執行拋出異常,則不會清空緩存例如:
@CachEvict(value=”testcache”,beforeInvocation=true)

基本原理

和 spring 的事務管理類似,spring cache 的關鍵原理就是 spring AOP,通過 spring AOP,其實現了在方法調用前、調用后獲取方法的入參和返回值,進而實現了緩存的邏輯。我們來看一下下面這個圖:

圖 2. 原始方法調用圖

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

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

圖 3. 動態代理調用圖

如上圖所示,這個時候,實際客戶端擁有的是一個代理的引用,那么在調用 foo() 方法的時候,會首先調用 proxy 的 foo() 方法,這個時候 proxy 可以整體控制實際的 pojo.foo() 方法的入參和返回值,比如緩存結果,比如直接略過執行實際的 foo() 方法等,都是可以輕松做到的。

擴展性

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

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

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

清單 23. MyCacheManager

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 內置的 AbstractCacheManager,實際上僅僅管理 MyCache 類的實例。

清單 24. MyCache

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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() { ??} }

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

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

清單 25. Spring-cache-anno.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <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

1 2 3 4 5 6 7 8 9 10 11 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()); }

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

清單 27. 運行結果

1 2 3 real querying db...someone passwd=null passwd=from mycache:accountCache

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

注意和限制

基于 proxy 的 spring aop 帶來的內部調用問題

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

清單 28. AccountService.java

1 2 3 4 5 6 7 8 9 public Account getAccountByName2(String userName) { ??return this.getAccountByName(userName); } @Cacheable(value="accountCache")// 使用了一個緩存名叫 accountCache public Account getAccountByName(String userName) { ??// 方法內部實現不考慮緩存邏輯,直接實現業務 ??return getFromDB(userName); }

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

清單 29. Main.java

1 2 3 4 5 6 7 8 9 10 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. 運行結果

1 2 3 real querying db...someone real querying db...someone real querying db...someone

可見,結果是每次都查詢數據庫,緩存沒起作用。要避免這個問題,就是要避免對緩存方法的內部調用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式來解決這個問題。

@CacheEvict 的可靠性問題

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

清單 31. AccountService.java

1 2 3 4 @CacheEvict(value="accountCache",allEntries=true)// 清空 accountCache 緩存 public void reload() { ??throw new RuntimeException(); }

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

清單 32. Main.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 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,然后再查詢一次,結果應該是只有第一次查詢走了數據庫,其他兩次查詢都從緩存,第三次也走緩存因為 reload 失敗了。

清單 33. 運行結果

1 real querying db...someone

和預期一樣。那么我們如何避免這個問題呢?我們可以用 @CacheEvict 注釋提供的 beforeInvocation 屬性,將其設置為 true,這樣,在方法執行前我們的緩存就被清空了。可以確保緩存被清空。

清單 34. AccountService.java

1 2 3 4 5 @CacheEvict(value="accountCache",allEntries=true,beforeInvocation=true) // 清空 accountCache 緩存 public void reload() { ??throw new RuntimeException(); }

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

執行相同的測試代碼

清單 35. 運行結果

1 2 real querying db...someone real querying db...someone

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

非 public 方法問題

和內部調用問題類似,非 public 方法如果想實現基于注釋的緩存,必須采用基于 AspectJ 的 AOP 機制,這里限于篇幅不再細述。

其他技巧

Dummy CacheManager 的配置和作用

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

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

清單 36. Spring-cache-anno.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <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="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 變為了 simpleCacheManager,且沒有配置 accountCache 實例,后面的 cacheManager 的實例是一個 CompositeCacheManager,他利用了前面的 simpleCacheManager 進行查詢,如果查詢不到,則根據標志位 fallbackToNoOpCache 來判斷是否不做任何緩存操作。

清單 37. 運行結果

1 2 3 real querying db...someone real querying db...someone real querying db...someone

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

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

清單 38. 運行結果

1 2 3 4 5 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 設置為 true 的情況下,系統會拋出異常。

小結

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

?

轉自:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

久久av在线 | 国产日韩欧美视频在线观看 | 中文字幕精品www乱入免费视频 | 国产视频一区二区在线 | 手机看片国产日韩 | av日韩精品 | 亚洲一区av | av一区二区三区在线播放 | 日日干天夜夜 | 在线视频app| 精品中文字幕在线观看 | 婷婷在线综合 | 91视频高清完整版 | 99久久日韩精品免费热麻豆美女 | 日韩大片在线 | 最近日本中文字幕 | 91成人短视频在线观看 | 午夜a区 | 国产伦精品一区二区三区免费 | 久久久91精品国产一区二区精品 | 91麻豆精品国产自产在线 | 四虎影视成人精品国库在线观看 | 91天天操| 日韩欧美精品在线观看 | 国产成人久久精品77777综合 | 香蕉视频久久 | 91精品国产欧美一区二区成人 | 青青啪 | 成人免费电影 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 久草在线播放视频 | 欧美一二三四在线 | 中文字幕免费 | 欧美一级黄色片 | 中文字幕日韩高清 | 亚洲专区 国产精品 | 日韩免费高清在线 | 在线视频99 | 黄色在线免费观看网址 | 九九色网 | 久久avav| 免费黄色av | 美女网站在线观看 | 精品久久免费看 | 综合国产视频 | 国产九九热 | 天堂在线一区二区 | 久久99热国产 | 91亚洲精品国偷拍自产在线观看 | 97色婷婷人人爽人人 | 国产福利一区二区在线 | 国产亚洲精品久 | 久久久久亚洲国产 | 西西www444| 国产色久 | 国产成人精品一区一区一区 | 久久国产三级 | 欧美极品xxx | 亚洲综合视频在线观看 | 最近中文字幕免费视频 | japanese黑人亚洲人4k | 2020天天干夜夜爽 | 永久免费精品视频网站 | 九色91在线| 国产精品一区在线播放 | 日韩av在线高清 | 综合网在线视频 | 人人爱人人做人人爽 | 波多野结衣视频一区二区三区 | 全久久久久久久久久久电影 | 精品福利视频在线 | 69精品人人人人 | 久草在线观看 | 久久久久欠精品国产毛片国产毛生 | 久久国产精品一二三区 | 中午字幕在线观看 | 日韩一区二区三区高清免费看看 | 国产香蕉97碰碰碰视频在线观看 | 人人干人人搞 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 999久久久久久久久久久 | 久久免费美女视频 | 精品成人久久 | 日韩高清免费无专码区 | 久草www| 亚洲欧美视频一区二区三区 | 日韩高清在线一区二区 | 96av麻豆蜜桃一区二区 | 欧美午夜剧场 | 日韩久久精品一区二区 | 中文字幕的 | 99免在线观看免费视频高清 | 欧美日本国产在线观看 | 成人资源在线观看 | 九九热视频在线 | 成人网大片 | 久久视频在线视频 | 久久久www成人免费精品 | 欧美aa在线观看 | 久久成人高清 | 黄色小说视频网站 | 亚洲国产精品久久久久婷婷884 | 久久福利综合 | 在线观看日本高清mv视频 | 天堂在线免费视频 | 免费黄色在线播放 | 久久精品亚洲综合专区 | 国产一区二区三区久久久 | 日韩av福利在线 | 日日日日 | 中文字幕乱偷在线 | 久久色在线播放 | 久久69精品久久久久久久电影好 | 亚洲区视频在线观看 | 99在线热播精品免费99热 | 精品国产一区二区三区蜜臀 | 亚洲午夜精品福利 | 丰满少妇在线观看网站 | 中文字幕av播放 | 91视频电影 | 天堂在线免费视频 | 欧美天堂久久 | 久久久这里有精品 | 国产自制av | 韩日电影在线观看 | 精品影院一区二区久久久 | 欧美日韩裸体免费视频 | 免费在线观看av电影 | 亚洲天天在线日亚洲洲精 | 天天干中文字幕 | 久久免费精品国产 | 日本午夜在线亚洲.国产 | 天天弄天天操 | 国产黄色成人av | 麻豆视频国产 | 在线v片免费观看视频 | 国产精品成人免费一区久久羞羞 | 久久天天综合网 | 久热爱| 久久久香蕉视频 | 久久久在线视频 | 精品一区二区在线观看 | 在线观看亚洲电影 | 欧美日韩成人一区 | 国产女做a爱免费视频 | 午夜在线日韩 | 国产高清免费在线播放 | 精品亚洲国产视频 | 国产精品免费小视频 | 国内精品久久久久久中文字幕 | 久色免费视频 | 一级a性色生活片久久毛片波多野 | 亚洲在线视频网站 | 美女国产免费 | 成人国产电影在线观看 | 久久99在线 | 日韩中文字幕在线不卡 | 99精品国自产在线 | 久久久999精品视频 国产美女免费观看 | 国产伦精品一区二区三区照片91 | 国产精品嫩草影院123 | 亚洲一区美女视频在线观看免费 | 91精品国产成人观看 | 久久国产免费看 | www.xxxx变态.com | 激情九九 | 少妇bbr搡bbb搡bbb | 国产一区二区影院 | 欧美午夜理伦三级在线观看 | 免费看三片 | av三级在线看 | 国产不卡在线 | 亚州黄色一级 | 99视频国产精品 | 麻豆久久 | 午夜狠狠干 | 中文字幕日韩无 | 九九热在线观看 | 婷婷www| 东方av在线免费观看 | 国产一级做a爱片久久毛片a | 在线看一区二区 | 99r在线观看 | 免费高清在线一区 | 91久久精品一区二区三区 | 91tv国产成人福利 | 激情网婷婷 | 国产成人精品在线观看 | 日韩大陆欧美高清视频区 | 国产精品理论视频 | av+在线播放在线播放 | 中文在线天堂资源 | 国产露脸91国语对白 | 久久久国产一区二区三区四区小说 | www.黄色片.com| av在线影片 | 国产精品免费视频久久久 | 在线观看亚洲精品视频 | 成人黄色在线看 | 91av原创 | 精品国产乱码久久久久久1区2匹 | 国产欧美精品一区二区三区 | 国产精品9区 | 久久久综合香蕉尹人综合网 | www四虎影院| 91av视频在线观看 | 97精品在线 | 久久久黄色av | 久久精品观看 | 91成人网页版 | 日韩精品大片 | 中文字幕 91 | 韩日av在线 | 中文字幕日韩免费视频 | 国模吧一区 | 中文字幕资源站 | 免费看毛片网站 | 免费日韩一区二区三区 | 精品国产一区二区三区四 | 91天堂在线观看 | 亚洲欧洲久久久 | 91色吧| 在线午夜av| 精品国产片 | 免费成人在线网站 | 91亚洲视频在线观看 | 欧美日韩不卡一区二区 | 精品亚洲免费 | 国产综合精品一区二区三区 | 色狠狠久久av五月综合 | 夜夜澡人模人人添人人看 | 精品国产大片 | 日韩理论| 狠狠搞,com| 91久久精品一区二区二区 | 久草在线免费新视频 | 国产91亚洲精品 | 国产99在线免费 | 欧美视频日韩视频 | 黄色一二级片 | 国产精品第10页 | 欧美精品被 | 中文字幕色综合网 | 天天人人综合 | 99高清视频有精品视频 | 欧美另类v | 一级成人网 | 91网站免费观看 | 国产免费资源 | 日韩视频在线观看视频 | 久久久午夜精品福利内容 | 久久天堂网站 | 国产黄色视 | 亚洲美女精品 | 激情av一区二区 | 97狠狠操 | 精品免费国产一区二区三区四区 | .国产精品成人自产拍在线观看6 | 天天操月月操 | 91av手机在线 | 国产高清视频色在线www | 人人草在线观看 | 久草在线99 | 国产精品va在线观看入 | 丁香花中文在线免费观看 | 97综合网 | 成年人在线免费看 | 亚洲国产精品一区二区久久hs | 99视频免费播放 | 在线观看免费黄视频 | 99久免费精品视频在线观看 | 最近中文字幕高清字幕免费mv | 探花视频免费观看 | 亚洲三级影院 | 97超碰香蕉 | 久久成人视屏 | 成人久久 | 91黄色免费网站 | 国产91在线观 | 97视频在线免费 | 久草在线久 | 亚洲欧洲国产精品 | 国产在线国产 | 亚洲黄在线观看 | 99精品国产免费久久 | 91人人澡人人爽人人精品 | 精品国产1区二区 | 国产高清成人av | 国产一区高清在线 | 久久免费av| 天天色天天上天天操 | 91视频91色| 香蕉精品视频在线观看 | 中文字幕资源在线观看 | 日本三级在线观看中文字 | 黄色小网站在线 | 蜜臀av在线一区二区三区 | 国产一区二区在线免费视频 | 国产日韩欧美视频在线观看 | 丁香婷婷在线 | 日韩精品视频免费在线观看 | 在线视频欧美日韩 | 欧美久久久一区二区三区 | 亚洲高清激情 | 天堂av在线 | 欧美日韩免费观看一区二区三区 | 亚洲综合成人在线 | 91一区啪爱嗯打偷拍欧美 | 激情大尺度视频 | 人人舔人人插 | 日韩三区在线 | 欧洲精品久久久久毛片完整版 | 久久久精品欧美一区二区免费 | 人人爽人人搞 | 国产精品色在线 | 日韩一三区 | 午夜精品电影 | 国产精品国产三级国产aⅴ9色 | 精品国产91亚洲一区二区三区www | 久久久久久久久久久电影 | 99精品观看 | 国产成人av| 久久成人亚洲欧美电影 | 免费看的国产视频网站 | 国产91粉嫩白浆在线观看 | 在线免费性生活片 | www.com.日本一级 | 国产成人精品一区二区三区在线观看 | 99视频在线观看一区三区 | 日日爽日日操 | 91视频国产免费 | 91香蕉视频| 97超碰超碰久久福利超碰 | 亚洲国产中文字幕在线观看 | 国产精品久久久久久一二三四五 | 久久一区二区三区国产精品 | 国产一区二区在线观看免费 | av 一区二区三区 | 99久久er热在这里只有精品66 | 91精品国产自产在线观看永久 | 精品一区二区在线免费观看 | 亚洲91精品在线观看 | 99视频免费播放 | 正在播放五月婷婷狠狠干 | 成人免费看黄 | 国内丰满少妇猛烈精品播 | 又黄又爽又刺激的视频 | 久久韩国免费视频 | www.婷婷色 | 伊人久操 | 欧美在线视频日韩 | 婷婷亚洲激情 | 一区二区精 | 久久免费视频这里只有精品 | 成年人看片 | 91免费高清观看 | 国产成人在线观看免费 | 国产成人亚洲在线电影 | 91av在线免费 | 日本黄色大片儿 | 日本视频久久久 | 99久久久久免费精品国产 | 中文字幕欧美激情 | 久久精品国产免费看久久精品 | 国产麻豆成人传媒免费观看 | 四虎www| 国产一区二区精品 | 免费人做人爱www的视 | 久久视影| 欧美va日韩va | 日韩91在线 | 精品欧美一区二区三区久久久 | 国产美女精品人人做人人爽 | wwwwww色| 久久精品91久久久久久再现 | 国产一区福利在线 | 国产又黄又猛又粗 | 一区二区三区免费播放 | 国际精品久久 | av成人黄色 | 精品国自产在线观看 | 久99久精品视频免费观看 | 国产精品久久久久久久久大全 | 国产一区二区精品久久91 | 人人草在线视频 | 亚洲成色 | 午夜精品久久久久99热app | 激情婷婷综合网 | 日韩亚洲欧美中文字幕 | 美国av大片 | 久久久久久国产精品亚洲78 | 国内免费的中文字幕 | 国产激情久久久 | 免费看特级毛片 | 91网站免费观看 | 国产剧情在线一区 | 免费观看一级视频 | 日日夜夜精品视频 | 在线观看视频免费播放 | 亚洲精品在线观看的 | 亚洲欧美乱综合图片区小说区 | 日韩欧美高清视频在线观看 | 99这里有精品 | 久久网址| 天天色天天射天天综合网 | 亚洲五月 | 亚洲免费专区 | 99在线视频播放 | 日韩激情精品 | 久久婷婷网 | 高清国产午夜精品久久久久久 | 三级黄色在线 | 国产精品久久久久婷婷二区次 | 久久av中文字幕片 | 国产资源精品在线观看 | 99在线视频观看 | 亚洲精品美女久久久久网站 | 一区二区三区精品在线视频 | av中文字幕在线电影 | 成人黄性视频 | 西西44人体做爰大胆视频 | 成年人看片 | 国产高清网站 | 人人看黄色 | 国产成人一区在线 | se婷婷| 人人澡超碰碰97碰碰碰软件 | 亚洲成人av一区 | 中文字幕五区 | 亚洲精选视频在线 | 久久性生活片 | 久久国产精品一区二区 | 久久午夜免费观看 | 99热 精品在线 | 午夜精品一区二区三区视频免费看 | www.狠狠操.com | 日韩精品在线观看视频 | 黄色午夜 | 91成人精品在线 | 国产又粗又猛又爽 | 色综合天 | 久久精品99精品国产香蕉 | 韩国一区二区三区视频 | 国产小视频免费在线网址 | 特级黄色一级 | 久久精品资源 | 91av在| 免费福利视频网站 | 公开超碰在线 | 天堂在线一区二区 | 日韩精品一区二区三区外面 | 97视频在线免费播放 | 偷拍精品一区二区三区 | 国产成人精品一区二区在线观看 | 久久免费视频一区 | 国产视频日韩视频欧美视频 | 美女视频黄网站 | 成年人国产视频 | 欧美一级xxxx | 亚洲1级片 | 色吊丝在线永久观看最新版本 | 国产精品美 | 免费看污的网站 | 91最新网址在线观看 | 欧美日在线 | 久久黄色美女 | 亚洲高清视频在线观看免费 | 色综合天天色 | 国产区精品视频 | 婷婷色在线观看 | 色婷婷激情电影 | 免费高清在线视频一区· | 天天鲁一鲁摸一摸爽一爽 | 免费99| 色久五月| 中文字幕中文中文字幕 | 成年人黄色免费网站 | 久久综合五月天 | 国产精品va视频 | 久久大视频 | 偷拍福利视频一区二区三区 | 西西www444 | 免费在线播放视频 | 91大神电影| 一级片免费观看视频 | 国产乱码精品一区二区三区介绍 | 亚洲精品999 | 久久综合九色综合欧美就去吻 | 国产不卡免费 | 天天干夜夜夜 | 国产一区视频免费在线观看 | 久久成人国产精品 | 久久综合网色—综合色88 | 免费在线国产精品 | 最新国产视频 | 国产网红在线观看 | 97超碰伊人| 69av国产| 国产日产精品一区二区三区四区 | 97网站| 国产精品一区二区三区在线播放 | 免费不卡中文字幕视频 | 在线观看黄av | 在线观看av免费观看 | 91香蕉视频色版 | 色在线免费视频 | 久草视频在线播放 | 久久久久一区二区三区四区 | 日韩视频中文字幕 | 国产精品18久久久久久久 | 97超碰在 | 国产丝袜高跟 | 色视频成人在线观看免 | 99精品视频在线观看免费 | 日韩免费在线播放 | 久草观看视频 | 欧美日韩国产一二 | 久久久免费网站 | 少妇av片| 欧美片网站yy | 91人人射 | 91丨九色丨91啦蝌蚪老版 | 麻豆视频免费观看 | 亚洲综合婷婷 | 国产精品久久久久久久av大片 | 国产亚洲婷婷 | 中文字幕免费国产精品 | 六月丁香综合网 | 在线视频观看你懂的 | 狠狠狠狠狠狠狠干 | 免费视频91蜜桃 | 国产精品2018 | 婷婷色网址 | 99久久婷婷国产一区二区三区 | 亚洲第一区精品 | 在线观看播放av | 一区二区中文字幕在线观看 | 操操操人人 | 天天操狠狠操夜夜操 | 久久电影中文字幕视频 | 四虎在线视频 | 午夜精品久久久久久久久久久 | 久久理论影院 | 国产福利免费在线观看 | 日本三级不卡 | 国产色网站 | 黄网站色成年免费观看 | 蜜桃传媒一区二区 | 99久久久久久久 | 992tv成人免费看片 | 国产一区在线精品 | 乱男乱女www7788 | 免费成人在线观看视频 | 中文字幕一区二区三区四区视频 | 午夜国产福利视频 | 91日韩免费 | 九色视频网站 | 日韩手机在线观看 | 久久99国产精品视频 | 久久久久夜色 | 六月丁香综合 | 中文字幕在线观看视频一区二区三区 | 国产高清视频在线免费观看 | 高清不卡免费视频 | 国产成人在线一区 | 日韩欧美有码在线 | 黄色大片免费播放 | 日韩网站一区二区 | 日韩一区二区三区在线观看 | 精品久久久久久国产偷窥 | 国产视频精品免费播放 | 国产色网站 | 亚洲成人av片 | 国产做爰视频 | 狠狠躁18三区二区一区ai明星 | 四虎国产精品免费 | 毛片1000部免费看 | 最新国产精品亚洲 | 手机在线看a | 欧美日韩国产区 | 在线91av | 可以免费观看的av片 | 国产在线精品国自产拍影院 | 一级片免费视频 | 一区二区三区四区五区六区 | 久久免费国产精品1 | 国产精品福利av | 久要激情网 | 久久精品久久精品久久39 | 欧美日韩一区二区三区不卡 | 三上悠亚在线免费 | 精品伦理一区二区三区 | 欧美日韩高清一区二区三区 | 色综合久久久久久久久五月 | 日韩理论影院 | 91在线网址 | 国产午夜精品一区二区三区嫩草 | 久久avav | 蜜臀av一区二区 | 99色亚洲| 一区在线电影 | 久久久亚洲成人 | 99爱在线| 国产麻豆精品久久 | 精品一二三四五区 | 国产亚洲一区 | 国内精品国产三级国产aⅴ久 | 国产99久久久久 | a天堂一码二码专区 | 日韩在线电影 | 久久99精品波多结衣一区 | 麻豆一区二区 | 男女激情片在线观看 | 天天操天天操天天操天天操天天操天天操 | 国产成人av网址 | 蜜臀久久99精品久久久酒店新书 | 91精品成人久久 | 五月婷婷av | 成人丝袜 | 国产短视频在线播放 | 久久久久亚洲精品中文字幕 | 久久久久日本精品一区二区三区 | 日本精品中文字幕在线观看 | 亚洲一区不卡视频 | 国产人成看黄久久久久久久久 | 激情视频综合网 | av色网站| 国产精品中文 | 在线视频欧美精品 | 亚洲精品视频在线观看免费视频 | 福利一区二区 | 免费a级毛片在线看 | 久久综合射 | 成人影视免费 | 免费在线黄色av | 日本在线观看中文字幕 | 久久久精品国产一区二区三区 | 日韩免费看| 黄色免费网站 | 好看的国产精品视频 | 精品播放| 最近能播放的中文字幕 | 国产一区二区久久久久 | 亚洲高清视频在线观看 | 九九热在线视频免费观看 | 欧美日韩在线第一页 | 午夜成人免费影院 | 婷婷伊人五月天 | 国产免费观看高清完整版 | 国产精品久久久久一区二区 | 国内小视频在线观看 | 欧美日韩网站 | 月丁香婷婷| 99久久久久成人国产免费 | 九九热在线观看视频 | 日日日操操 | 亚洲人片在线观看 | 亚洲日韩精品欧美一区二区 | av在线等| 成人观看| 亚洲国产日韩一区 | 99久久久久免费精品国产 | 免费精品视频在线观看 | 在线播放日韩 | 亚洲成a人片77777潘金莲 | 91久久精品一区二区二区 | 最近中文字幕高清字幕在线视频 | 噜噜色官网 | 国内外成人免费在线视频 | 又湿又紧又大又爽a视频国产 | 国产在线欧美 | 欧美亚洲免费在线一区 | 青草视频在线 | 国产精品久久一区二区无卡 | 成人午夜影视 | 99热都是精品 | 久久一二三四 | 人人人爽| 天天射天天射 | 97av视频| 在线国产91 | 国产精品va视频 | 日韩av网页| 久久精品视频免费观看 | 免费在线成人av电影 | 久久国产精品久久久 | 日本中文一级片 | 色婷婷av一区 | 在线观看国产高清视频 | 日韩欧美高清不卡 | 久久看毛片 | 日韩电影在线看 | 日日日日日 | 成人在线视频一区 | www黄免费 | 日韩高清在线一区二区三区 | 91在线区 | 在线观看亚洲免费视频 | 四月婷婷在线观看 | 欧美精品成人在线 | 91成人精品一区在线播放69 | 久久精品99精品国产香蕉 | 日韩免费视频一区二区 | 欧美二区视频 | 国产精品久久嫩一区二区免费 | 亚洲资源视频 | 国产精品 欧美 日韩 | 久久在线观看视频 | av片一区二区 | 在线免费观看黄网站 | 色吊丝在线永久观看最新版本 | 亚洲成av人片一区二区梦乃 | 亚洲欧洲视频 | 久久精品直播 | 国产专区一 | www.夜夜夜 | wwxxxx日本 | 99久久久久久久 | 狠狠伊人 | 国产在线不卡一区 | 午夜色影院 | 亚洲精品黄色片 | 毛片黄色一级 | 日韩大片在线观看 | 在线成人一区 | 玖草影院 | 国产自产在线视频 | 国产精品视频久久 | 99视频免费看 | 99热国内精品 | 伊人婷婷在线 | 91亚色免费视频 | 国产精品免费视频久久久 | 亚洲永久精品在线观看 | 一区二精品 | 岛国一区在线 | 免费无遮挡动漫网站 | 精品国产不卡 | 国产精品日韩在线 | 久久伊人爱 | 国产精品久久久久久爽爽爽 | 24小时日本在线www免费的 | 午夜精品一区二区三区视频免费看 | 四虎影视国产精品免费久久 | 五月天电影免费在线观看一区 | 久草在线高清视频 | 久久综合狠狠综合 | 四虎影视成人永久免费观看亚洲欧美 | 精品国产一区二区久久 | av资源网在线播放 | 在线免费观看国产视频 | 日日干天天操 | 激情欧美xxxx | 色在线最新 | 99久久久国产精品免费观看 | 天天干天天草 | 国产黄色精品视频 | 免费看黄色小说的网站 | 国产视频在线一区二区 | 国产欧美最新羞羞视频在线观看 | 亚洲高清91 | 日韩在线视 | 中文字幕 第二区 | 91在线精品秘密一区二区 | av天天草| 成人在线观看av | 日韩精品一卡 | 成人国产在线 | 欧美成人黄色 | 色欧美日韩 | 婷婷在线免费 | 免费国产一区二区视频 | 在线观看av的网站 | 日韩中文在线电影 | 天天射成人 | 久久人网 | 久久精品视频在线免费观看 | 免费观看第二部31集 | 日本一区二区不卡高清 | 国产色黄网站 | 久久久久女人精品毛片 | 国产色爽 | 久久精品二区 | 色丁香色婷婷 | 久久国产露脸精品国产 | 色综合在 | 久久久久久蜜av免费网站 | 中文字幕乱码亚洲精品一区 | 97色在线视频| 99国产精品久久久久老师 | 久久黄色精品视频 | 91av片| 国产精品福利视频 | 欧美aa一级片 | 欧美日韩性视频在线 | 久久激情小视频 | 精品国产成人av在线免 | 欧美在线91 | 国产精品原创视频 | 夜夜躁狠狠燥 | 精品一区中文字幕 | 在线观看中文字幕 | 狠狠色丁香婷婷综合久小说久 | 国产午夜三级 | 91九色蝌蚪视频在线 | 九草在线视频 | 五月婷婷.com | 成人午夜精品福利免费 | 四虎免费在线观看视频 | 9999免费视频 | 国产精品手机在线观看 | 中文永久免费观看 | 一级一片免费视频 | 久久久免费精品视频 | 亚洲黄色av网址 | 久久久久久久久久久网站 | 亚洲一区二区精品视频 | 手机看片福利 | 天天天天爱天天躁 | 91在线资源 | 夜夜躁日日躁狠狠躁 | 国产精品久久久久久久久久尿 | 久草成人在线 | 在线观看国产福利片 | 国产精品欧美一区二区 | 国产精久久 | 美女免费电影 | 国产高清不卡一区二区三区 | 日韩精品无 | 精品国产一区二区三区在线观看 | 久久久久久久久久久久久国产精品 | 国产精品国产三级国产不产一地 | 99精品在线视频观看 | 免费福利在线播放 | 国产色在线,com | 成年人黄色av | 黄网站污 | 一区二区三区日韩在线观看 | 91网免费观看 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 狠色狠色综合久久 | 毛片网站在线 | 久久久久一区二区三区四区 | 麻豆国产在线播放 | 69亚洲乱 | 亚洲中字幕 | 91视频免费看 | 欧美另类xxxxx | 91超级碰 | 精品主播网红福利资源观看 | 中文字幕在线免费看 | 中文字幕婷婷 | 丁香色天天| 久草视频在线免费看 | a v在线观看 | 日日夜夜综合网 | 黄色网在线免费观看 | 国产激情小视频在线观看 | 18av在线视频 | 国产999久久久 | 91自拍视频在线 | 国产手机在线 | 国产精品毛片 | 日韩字幕在线观看 | 成人亚洲精品国产www | 欧美日韩中文国产一区发布 | 91av看片| 五月婷网 | 欧美日韩二三区 | 人人干天天射 | 天天综合网天天综合色 | 久久99精品国产麻豆宅宅 | 天天操天天综合网 | 贫乳av女优大全 | 久久久久国产精品免费 | 欧美精品一区在线 | 久久优| 91麻豆免费视频 | 狠狠干 狠狠操 | 黄色成人影院 | 久久国产精品免费一区 | 国产欧美精品一区二区三区 | 亚洲三级性片 | 婷婷色视频 | 亚洲精品国产精品国自产在线 | 免费看搞黄视频网站 | 激情视频一区 | 999成人网| 美女视频久久黄 | 干亚洲少妇 | 久久综合九色99 | 天天干天天操天天干 | 天天玩夜夜操 | 国产色黄网站 | 亚洲黄色免费观看 | 国产成人av网 | 日韩av一区二区三区在线观看 | 少妇bbb搡bbbb搡bbbb′ | 热久久免费视频 | 黄色av免费电影 | 亚洲一区二区视频在线播放 | av电影一区二区 | 美女久久久久久 | 国产一区二区在线免费播放 | 9在线观看免费高清完整 | 国产一二区视频 | 国产日韩精品一区二区三区在线 | 日韩免费高清 | 久久伊人婷婷 | 精品国产一区二区三区在线 | 国产视频不卡 | 国产啊v在线 | 国产精品入口66mio女同 | 亚洲精品美女久久久久网站 | 欧美另类调教 | 91丨九色丨蝌蚪丰满 | 97超碰免费 | 久久综合欧美精品亚洲一区 | 日韩欧美亚洲 | 狠狠色狠狠色综合日日92 | 亚洲精品网站 | 国产精品成人久久 | 国产精品亚洲视频 | 久久精品二区 | 亚洲国产中文字幕在线观看 | 99在线观看精品 | 精品国内自产拍在线观看视频 | 国产爽妇网 | 日韩黄色一级电影 | 亚洲日本在线视频观看 | 九月婷婷综合网 | 日韩精品第一区 | 成人一区二区在线观看 | 国产1区2区3区精品美女 | 九九热久久免费视频 | 日本91在线 | 国产不卡av在线 | 91一区二区在线 | 成人免费毛片aaaaaa片 | 在线观看成人福利 | 午夜久久电影网 | 日韩精品视频在线观看免费 | 国产精品每日更新 | 免费av片在线 | 日日操日日插 | 国产九九精品视频 | 国产精品美女久久久 | 97天天干 | 久久精品99北条麻妃 | 免费观看91视频大全 | 天天干干| av永久网址 | 国产成人精品在线 | 91.精品高清在线观看 | 精品国产一区二区三区四区vr | av一级片在线观看 | 国产精品观看视频 | 国产无吗一区二区三区在线欢 | 国产一级二级在线 | 国产在线精品区 | 一区在线免费观看 | 久久久久欠精品国产毛片国产毛生 | 一区二区视频欧美 | 久久人人爽爽人人爽人人片av | 欧美一级在线看 | 97超碰人人模人人人爽人人爱 | 久草在线观看视频免费 | 在线视频日韩欧美 | 91久久人澡人人添人人爽欧美 | 日韩精品国产一区 | 国产中文字幕网 | 人人澡澡人人 | 亚洲专区欧美 | 欧美综合久久 | 五月激情姐姐 | 亚洲精品美女久久久 | 国产九色视频在线观看 | 国产xxxxx在线观看 | 国产伦精品一区二区三区在线 | 亚洲国产色一区 | 人人爽影院| 国内精品视频在线播放 | 国产精品免费小视频 | 最新中文字幕视频 | 日韩色一区二区三区 | 色网av| 黄色大全免费观看 | 国产精品一区二区久久精品 | 国产精品theporn | 欧美激情综合五月色丁香小说 | www.亚洲精品在线 | 国产精品视屏 | 91香蕉久久 | 99999精品视频 | 免费看久久 | 天天操婷婷 | 天天操天天摸天天射 | 国产成人精品三级 |