在設計本身的緩存框架以前,有必要了解一下spring的cache模塊。在spring3.1及之後的版本中,提供了基於註解的緩存支持,但spring並無對緩存進行具體實現(除了提供一個簡單的基於Map的實現以外)。本框架就是在此基礎上進行擴展。spring
1、spring經過註解操做緩存的使用方法與示例:數據庫
由於篇幅有限,網上有不少現成的使用例子,這裏不介紹spring緩存具體使用方法。設計模式
2、spring經過註解操做緩存的原理:api
spring能經過註解操做緩存,是由於它採用天生就具備的強大的AOP機制,攔截業務方法。整體流程是:當一個方法上配置了Cacheable之類的註解後,這個方法被調用時,就會被一個叫CacheInterceptor的攔截器攔截,進入該類的invoke()方法中,若是當前context已經初始化完成,該方法緊接着會調用execute()。execute()方法中會讀取原來被調用業務方法上的註解信息,經過這些信息進行相應的緩存操做,再跟據操做的結果決定是否調用原方法中的業務邏輯。這就是spring經過註解操做緩存的整體流程。緩存
CacheInterceptor是在srping上下文初始化的時候,經過配置文件中的<cache:annotation-driven />標籤註冊到context中的。此標籤的cache名稱空間對應的處理類的是org.springframework.cache.config. CacheNamespaceHandler,其中對於cache:annotation-driven標籤的解析類是org.springframework.cache.config. AnnotationDrivenCacheBeanDefinitionParser(在CacheNamespaceHandler類的代碼中能夠找到),在這個類的parse()解析方法中跟據annotation-driven標籤中的model屬性決定是經過代理的方式實現 aop仍是經過aspectj的方式進行切面攔截(默認採用proxy代理方式)。對於代理方式,會調用registerCacheAdvisor()註冊緩存Advisor。在這個方法中會注入一個CacheInterceptor類型的攔截器。這就實現了對業務方法的切面攔截。微信
在registerCacheAdvisor()方法中,還會調用一個叫parseCacheResolution()方法來注入一個緩存解析器CacheResolver,若是<cache:annotation-driven />標籤中有cache-resolver的配置,就跟據配置注入一個CacheResolver,不然,就默認注入一個SimpleCacheResolver類型的實例。每一個CacheResolver中包裝了一個CacheManager,這個CacheManager可經過<cache:annotation-driven />標籤中cacheManager之類的配置進行指定,若是沒有指定,會自動注入一個叫cacheManager的bean。框架
CacheInterceptor在執行execute()的過程當中會調用事先注入的CacheResolver實例的resolveCaches()方法解析業務方法中須要操做的緩存Cache列表(resolveCaches()方法內部實現是經過調用此CacheResolver實例中的cacheManager屬性的getCache()方法獲取Cache)。獲取到須要操做的Cache列表後,遍歷這個列表,而後都過調用doGet(Cache cache)或doPut(Cache cache)方法進行緩存操做。doGet()或doPut()方法在CacheInterceptor類的父類AbstractCacheInvoker中定義(注意這裏的Cache列表,是spring包裝了特定廠商緩存後的Cache對像,是org.springframework.cache.Cache類型的實例)。spa
整體上說,CacheInterceptor的execute()中對緩存的操做就是經過事先注一個CacheResolver和CacheManager實例,而後經過調用這個CacheResolver實例的resolveCaches()得到須要操做的Cache列表,再遍歷列表,將每一個Cache實例做爲參數傳入doGet()或doPut()來實現緩存讀取。固然,還須要一些Key之類的參數,這個是由keyGenerator自動生成的。對於keyGenerator,這裏再也不介紹。看看源碼就很容易理清思路。設計
3、spring緩存模塊的類簡介:代理
spring緩存模塊對可用的Cache採用適配器模式進行了統一的封裝。具體代碼在spring-context-xxx.jar包中的org.springframework.cache.Cache接口,此接口聲明一些儲如get(Object key),put(Object key,Object value)等緩存操做的統一api。在這個接口中,還聲明瞭一個叫getNativeCache()的方法,返回它適配的具體的緩存實現(好比在集成ehcache時,這個接口實現類的實例調用getNativeCache()時會返回net.sf.ehcache.Cache類型的實例)。每個Cache實例都經過名稱加以區分,因此在Cache接口中,還聲明瞭一個getName()返回此實例的名稱。spring提供一個叫ConcurrentMapCache的基於Map的Cache實現類,做爲它內置的本地緩存實現方案。
Cache相關的類圖以下:
全部被包裝的Cache,都由CacheManager實例進行統一管理(在上文的原理分析中能夠看到,在<cache:annotation-driven />標籤的解析過程當中會自動注入一個CacheManager實例),他提供一個叫getCache(String name)的方法,跟據名稱得到一個被包裝的Cache。
在Srping的緩存模塊中,spring-context-xxx.jar包中自帶一個叫ConcurrentMapCacheManagr的簡單實現類,它能夠管理上文的提到的ConcurrentMapCache類。開發人員若是配置了此管理器,也就擁有了本地緩存的能力。另外,爲了讓應用支持同時存在多個CacheManager,spring提供了一個CompositeCacheManager的實現類,以組合設計模式的方式統一管理多個CacheManager實例。
CacheManager部分類圖以下:
上圖中,CacheManager接口中只有兩個方法getCache(String)和getCacheNames(),顯然,這兩個方法的做用就是跟據名稱得到Cache實例以及得到全部被管理的緩存的名稱列表。
上圖中CompositeCacheManager與ConcurrentMapCacheManager類的做用在前文已經介紹過了。這裏簡單再說一下具體實現的方式:CompositeCacheManager中維護一個CacheManager列表,用戶能夠經過配置,把多個CacheManager配置到這個列表中,使得應用能夠同時管理多個緩存管理器。這個類對於getCache(String)方法實現是經過遍歷這個列表,匹配出name相同的Cache實例並返回。這個類還能夠經過配置指定一個boolean的fallbackToNoOpCache標誌屬性,它的做用就是,當經過getCache(string)獲取不到Cache實例時,是否不進行任何緩存操做。在默認狀況或者fallbackToNoOpCache值爲false時,在經過getCache(string)獲取不到Cache實例時,業務層上可能會拋出運行時異常(好比提示「找不到XXX名稱的Cache」)。但若是爲true時,這時候不進行任何緩存操做也不拋異常,這種場景主要用於在不具有緩存條件的時候,在不改代碼的狀況下,禁用緩存。spring對於這種機制的實現,是經過上圖中沒有畫出的兩個特殊的類來實現的: NoOpCacheManager和NoOpCache類。這兩個類分別是CacheManager類Cache類的子類,表示不進行任何緩存操做。
在ConcurrentMapCacheManager內置的緩存管理器中,能夠經過配置指定一個boolean類型的allowNullValues屬性,用於指定緩存中可否保存null值。由於該管理器是用於spring經過Map實現的內置緩存的管理器實現。在對應的Cache實現類ConcurrentMapCache中能夠看到,它是經過ConcurrentHashMap保存全部建值對數據的。然而ConcurrentHashMap並不支持保存null值,直接在ConcurrentHashMap中put空值會拋空指針異常。然而,往緩存中保存空值有時候確實也是有必要的。好比,在從數據庫查詢某項數據時,因數據不存在,返回了null。這時候若是不把這個null值保存到緩存中去,那麼下次再做查詢時,緩存就沒法命中,從而致使重複查詢數據庫,這就是所謂的緩存穿透。爲了防止這種狀況,這就要對null值作一個包裝,把它包裝成一個非null的並且在業務上認爲是無效的對像保存到緩存裏面。ConcurrentMapCacheManager中的allowNullValues就是用於指定可否緩存null,若是此值爲true,將把自動把null包裝成無效對像緩存起來,若是爲false,那麼須要開發人員自行從業務層上保證不往緩存中保存null數據。
在上面類圖中,最重要的是類就是AbstractCacheManager抽象類了,它只對CacheManager提供了一個簡單實現,並開放了一些好比loadCaches()之類的抽象方法,對於這個類的具體實現,由須要集成的具體的緩存廠商來實現。
AbstractCacheManager類除了實現CacheManager接口以外,還實現了srping框架的InitializingBean接口,這使得此類型的bean 在被spring初始化的時候,會自動調用afterPropertiesSet()方法,這個方法會調用此類的initializeCaches()的方法進行初始化。它的具體邏輯是經過用調用抽象方法loadCaches()獲取它能管理的全部Cache實列列表,並遍歷它,把它都添加到此實例的cacheMap屬性集合中,同時把全部的name都統一加入到cacheNames集合中,以便方法getCacheNames()能夠返回全部cache的名稱集合。
抽象方法loadCaches()的做用是從具體的緩存實現中加載全部它能管理的Cache(好比調用EhCache相關的api加載他全部的Cache)。
在這個類中,最重要的方法就是getCache(String),表示經過緩存名稱獲取緩存實例。它的實現邏輯是,先從cacheMap集合以name做爲key查找cache,若是找不到,就調用getMissingCache()方法獲取。這個getMissingCache()意圖爲:返回loadCaches()原來沒有加載到的Cache(這裏有一次從新加載的機會)。固然,AbstractCacheManager這個類並無對這個方法作特別的實現,只是簡單返回了null,具體的實現類能夠覆蓋這個方法。
這個類中還有其它的諸如addCache()之類的方法,就不介紹了。下面看看spring自帶的ehcache實現。
4、spring cache內置的ehcache支持方案:
spring cache模塊經過提供ehcache相關的幾個實現類對ehcache進行支持,採用的是適配器設計模式,相關類在srping-cotext-suppoert-xxx.jar包中以下類圖:
圖中可看出,最重要的兩個類就是EhCacheCache和EhCacheCacheManager,分別是Cache和AbstractCacheManager的子類實現類。這兩個類中,對於父類抽象的方法的實現,都是委託它類部的Ehcache及net.sf.ehcache.CacheManager經過調用ehcache相關api來完成。
然而,由於ehcache的配置參數比較多,爲了方便開發人員簡潔的配置EhCacheCache和EhCacheCacheManager實例。spring提供了兩個對應的工廠類: EhCacheFactoryBean和EhCacheManagerFactoryBean。開發人員只要在配置文件中配置這兩個類型的bean,就能夠很方便的完成與ehcache的集成 。
4、集成memcache方案提示:
spring並無提供對memcache的直接支持,須要咱們本身實現,經過以上分析,能夠想象,集成memcache的主要思路就是實現本身的CacheManager及Cache類。限於篇幅,在後面的文章中再給出具體的實現方案。
想了解更多信息的同窗們能夠掃如下二維碼關注個人微信公衆號: