天天看點

ehcache +mybatis+spring 自定義緩存政策1       基于注解的支援2       配置Spring對Cache的支援3       鍵的生成政策4       Spring單獨使用Ehcache

從3.1開始,spring引入了對cache的支援。其使用方法和原理都類似于spring對事務管理的支援。spring cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和傳回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時将不再執行該方法,而是直接從緩存中擷取結果進行傳回。是以在使用spring cache的時候我們要保證我們緩存的方法對于相同的方法參數要有相同的傳回結果。

       使用spring cache需要我們做兩方面的事:

n  聲明某些方法使用緩存

n  配置spring對cache的支援

       和spring對事務管理的支援一樣,spring對cache的支援也有基于注解和基于xml配置兩種方式。下面我們先來看看基于注解的方式。

       spring為我們提供了幾個注解來支援spring cache。其核心主要是@cacheable和@cacheevict。使用@cacheable标記的方法在執行後spring cache将緩存其傳回結果,而使用@cacheevict标記的方法會在方法執行前或者執行後移除spring cache中的某些元素。下面我們将來詳細介紹一下spring基于注解對cache的支援所提供的幾個注解。

       @cacheable可以标記在一個方法上,也可以标記在一個類上。當标記在一個方法上時表示該方法是支援緩存的,當标記在一個類上時則表示該類所有的方法都是支援緩存的。對于一個支援緩存的方法,spring會在其被調用後将其傳回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中擷取結果,而不需要再次執行該方法。spring在緩存方法的傳回值時是以鍵值對進行緩存的,值就是方法的傳回結果,至于鍵的話,spring又支援兩種政策,預設政策和自定義政策,這個稍後會進行說明。需要注意的是當一個支援緩存的方法在對象内部被調用時是不會觸發緩存功能的。@cacheable可以指定三個屬性,value、key和condition。

       value屬性是必須指定的,其表示目前方法的傳回值是會被緩存在哪個cache上的,對應cache的名稱。其可以是一個cache也可以是多個cache,當需要指定多個cache時其是一個數組。

   @cacheable("cache1")//cache是發生在cache1上的

   public user find(integer id) {

      returnnull;

   }

   @cacheable({"cache1", "cache2"})//cache是發生在cache1和cache2上的

       key屬性是用來指定spring緩存方法的傳回結果時對應的key的。該屬性支援springel表達式。當我們沒有指定該屬性時,spring将使用預設政策生成key。我們這裡先來看看自定義政策,至于預設政策會在後文單獨介紹。

       自定義政策是指我們可以通過spring的el表達式來指定我們的key。這裡的el表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”。下面是幾個使用參數作為key的示例。

   @cacheable(value="users", key="#id")

   @cacheable(value="users", key="#p0")

   @cacheable(value="users", key="#user.id")

   public user find(user user) {

   @cacheable(value="users", key="#p0.id")

       除了上述使用方法參數作為key之外,spring還為我們提供了一個root對象可以用來生成key。通過該root對象我們可以擷取到以下資訊。

屬性名稱

描述

示例

methodname

目前方法名

#root.methodname

method

目前方法

#root.method.name

target

目前被調用的對象

#root.target

targetclass

目前被調用的對象的class

#root.targetclass

args

目前方法參數組成的數組

#root.args[0]

caches

目前被調用的方法使用的cache

#root.caches[0].name

       當我們要使用root對象的屬性作為key時我們也可以将“#root”省略,因為spring預設使用的就是root對象的屬性。如:

   @cacheable(value={"users", "xxx"}, key="caches[1].name")

       有的時候我們可能并不希望緩存一個方法所有的傳回結果。通過condition屬性可以實作這一功能。condition屬性預設為空,表示将緩存所有的調用情形。其值是通過springel表達式來指定的,當為true時表示進行緩存處理;當為false時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。如下示例表示隻有當user的id為偶數時才會進行緩存。

   @cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")

      system.out.println("find user by user " + user);

      return user;

       在支援spring cache的環境下,對于使用@cacheable标注的方法,spring在每次執行前都會檢查cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中擷取結果進行傳回,否則才會執行并将傳回結果存入指定的緩存中。@cacheput也可以聲明一個方法支援緩存功能。與@cacheable不同的是使用@cacheput标注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,并将執行結果以鍵值對的形式存入指定的緩存中。

       @cacheput也可以标注在類上和方法上。使用@cacheput時我們可以指定的屬性跟@cacheable是一樣的。

   @cacheput("users")//每次都會執行方法,并将結果存入指定的緩存中

       @cacheevict是用來标注在需要清除緩存元素的方法或類上的。當标記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@cacheevict可以指定的屬性有value、key、condition、allentries和beforeinvocation。其中value、key和condition的語義與@cacheable對應的屬性類似。即value表示清除操作是發生在哪些cache上的(對應cache的名稱);key表示需要清除的是哪個key,如未指定則會使用預設政策生成的key;condition表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性allentries和beforeinvocation。

       allentries是boolean類型,表示是否需要清除緩存中的所有元素。預設為false,表示不需要。當指定了allentries為true時,spring cache将忽略指定的key。有的時候我們需要cache一下清除所有的元素,這比一個一個清除元素更有效率。

   @cacheevict(value="users", allentries=true)

   public void delete(integer id) {

      system.out.println("delete user by id: " + id);

       清除操作預設是在對應方法成功執行之後觸發的,即方法如果因為抛出異常而未能成功傳回時也不會觸發清除操作。使用beforeinvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,spring會在調用該方法之前清除緩存中的指定元素。

   @cacheevict(value="users", beforeinvocation=true)

       其實除了使用@cacheevict清除緩存元素外,當我們使用ehcache作為實作時,我們也可以配置ehcache自身的驅除政策,其是通過ehcache的配置檔案來指定的。由于ehcache不是本文描述的重點,這裡就不多贅述了,想了解更多關于ehcache的資訊,請檢視我關于ehcache的專欄。

       @caching注解可以讓我們在一個方法或者類上同時指定多個spring cache相關的注解。其擁有三個屬性:cacheable、put和evict,分别用于指定@cacheable、@cacheput和@cacheevict。

   @caching(cacheable = @cacheable("users"), evict = { @cacheevict("cache2"),

         @cacheevict(value = "cache3", allentries = true) })

       spring允許我們在配置可緩存的方法時使用自定義的注解,前提是自定義的注解上必須使用對應的注解進行标注。如我們有如下這麼一個使用@cacheable進行标注的自定義注解。

@target({elementtype.type, elementtype.method})

@retention(retentionpolicy.runtime)

@cacheable(value="users")

public @interface mycacheable {

}

       那麼在我們需要緩存的方法上使用@mycacheable進行标注也可以達到同樣的效果。

   @mycacheable

   public user findbyid(integer id) {

      system.out.println("find user by id: " + id);

      user user = new user();

      user.setid(id);

      user.setname("name" + id);

       配置spring對基于注解的cache的支援,首先我們需要在spring的配置檔案中引入cache命名空間,其次通過<cache:annotation-driven />就可以啟用spring對基于注解的cache的支援。

<?xml version="1.0" encoding="utf-8"?>

<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"

   xsi:schemalocation="http://www.springframework.org/schema/beans

     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

     http://www.springframework.org/schema/cache

     http://www.springframework.org/schema/cache/spring-cache.xsd">

   <cache:annotation-driven/>

</beans>

       <cache:annotation-driven/>有一個cache-manager屬性用來指定目前所使用的cachemanager對應的bean的名稱,預設是cachemanager,是以當我們的cachemanager的id為cachemanager時我們可以不指定該參數,否則就需要我們指定了。

       <cache:annotation-driven/>還可以指定一個mode屬性,可選值有proxy和aspectj。預設是使用proxy。當mode為proxy時,隻有緩存方法在外部被調用的時候spring

cache才會發生作用,這也就意味着如果一個緩存方法在其聲明對象内部被調用時spring cache是不會發生作用的。而mode為aspectj時就不會有這種問題。另外使用proxy時,隻有public方法上的@cacheable等标注才會起作用,如果需要非public方法上的方法也可以使用spring

cache時把mode設定為aspectj。

       此外,<cache:annotation-driven/>還可以指定一個proxy-target-class屬性,表示是否要代理class,預設為false。我們前面提到的@cacheable、@cacheevict等也可以标注在接口上,這對于基于接口的代理來說是沒有什麼問題的,但是需要注意的是當我們設定proxy-target-class為true或者mode為aspectj時,是直接基于class進行操作的,定義在接口上的@cacheable等cache注解不會被識别到,那對應的spring

cache也不會起作用了。

       需要注意的是<cache:annotation-driven/>隻會去尋找定義在同一個applicationcontext下的@cacheable等緩存注解。

       除了使用注解來聲明對cache的支援外,spring還支援使用xml來聲明對cache的支援。這主要是通過類似于aop:advice的cache:advice來進行的。在cache命名空間下定義了一個cache:advice元素用來定義一個對于cache的advice。其需要指定一個cache-manager屬性,預設為cachemanager。cache:advice下面可以指定多個cache:caching元素,其有點類似于使用注解時的@caching注解。cache:caching元素下又可以指定cache:cacheable、cache:cache-put和cache:cache-evict元素,它們類似于使用注解時的@cacheable、@cacheput和@cacheevict。下面來看一個示例:

   <cache:advice id="cacheadvice" cache-manager="cachemanager">

      <cache:caching cache="users">

         <cache:cacheable method="findbyid" key="#p0"/>

         <cache:cacheable method="find" key="#user.id"/>

         <cache:cache-evict method="deleteall" all-entries="true"/>

      </cache:caching>

   </cache:advice>

       上面配置定義了一個名為cacheadvice的cache:advice,其中指定了将緩存findbyid方法和find方法到名為users的緩存中。這裡的方法還可以使用通配符“*”,比如“find*”表示任何以“find”開始的方法。

       有了cache:advice之後,我們還需要引入aop命名空間,然後通過aop:config指定定義好的cacheadvice要應用在哪些pointcut上。如:

   <aop:config proxy-target-class="false">

      <aop:advisor advice-ref="cacheadvice" pointcut="execution(*

com.xxx.userservice.*(..))"/>

   </aop:config>

       上面的配置表示在調用com.xxx.userservice中任意公共方法時将使用cacheadvice對應的cache:advice來進行spring cache處理。更多關于spring aop的内容不在本文讨論範疇内。

       cachemanager是spring定義的一個用來管理cache的接口。spring自身已經為我們提供了兩種cachemanager的實作,一種是基于java api的concurrentmap,另一種是基于第三方cache實作——ehcache,如果我們需要使用其它類型的緩存時,我們可以自己來實作spring的cachemanager接口或abstractcachemanager抽象類。下面分别來看看spring已經為我們實作好了的兩種cachemanager的配置示例。

   <bean id="cachemanager" class="org.springframework.cache.support.simplecachemanager">

      <property name="caches">

         <set>

            <bean class="org.springframework.cache.concurrent.concurrentmapcachefactorybean" p:name="xxx"/>

         </set>

      </property>

   </bean>

       上面的配置使用的是一個simplecachemanager,其中包含一個名為“xxx”的concurrentmapcache。

   <!-- ehcache實作 -->

   <bean id="cachemanager" class="org.springframework.cache.ehcache.ehcachecachemanager" p:cache-manager-ref="ehcachemanager"/>

   <bean id="ehcachemanager" class="org.springframework.cache.ehcache.ehcachemanagerfactorybean" p:config-location="ehcache-spring.xml"/>

       上面的配置使用了一個spring提供的ehcachecachemanager來生成一個spring的cachemanager,其接收一個ehcache的cachemanager,因為真正用來存入緩存資料的還是ehcache。ehcache的cachemanager是通過spring提供的ehcachemanagerfactorybean來生成的,其可以通過指定ehcache的配置檔案位置來生成一個ehcache的cachemanager。若未指定則将按照ehcache的預設規則取classpath根路徑下的ehcache.xml檔案,若該檔案也不存在,則擷取ehcache對應jar包中的ehcache-failsafe.xml檔案作為配置檔案。更多關于ehcache的内容這裡就不多說了,它不屬于本文讨論的内容,欲了解更多關于ehcache的内容可以參考我之前釋出的ehcache系列文章,也可以參考官方文檔等。

       鍵的生成政策有兩種,一種是預設政策,一種是自定義政策。

       預設的key生成政策是通過keygenerator生成的,其預設政策如下:

n  如果方法沒有參數,則使用0作為key。

n  如果隻有一個參數的話則使用該參數作為key。

n  如果參數多餘一個的話則使用所有參數的hashcode作為key。

       如果我們需要指定自己的預設政策的話,那麼我們可以實作自己的keygenerator,然後指定我們的spring cache使用的keygenerator為我們自己定義的keygenerator。

       使用基于注解的配置時是通過cache:annotation-driven指定的.

   <cache:annotation-driven key-generator="userkeygenerator"/>

   <bean id="userkeygenerator" class="com.xxx.cache.userkeygenerator"/>

       而使用基于xml配置時是通過cache:advice來指定的。

   <cache:advice id="cacheadvice" cache-manager="cachemanager" key-generator="userkeygenerator">

       需要注意的是此時我們所有的cache使用的key的預設生成政策都是同一個keygenerator。

       前面介紹的内容是spring内置的對cache的支援,其實我們也可以通過spring自己單獨的使用ehcache的cachemanager或ehcache對象。通過在application context中配置ehcachemanagerfactorybean和ehcachefactorybean,我們就可以把對應的ehcache的cachemanager和ehcache對象注入到其它的spring bean對象中進行使用。

     ehcachemanagerfactorybean是spring内置的一個可以産生ehcache的cachemanager對象的factorybean。其可以通過屬性configlocation指定用于建立cachemanager的ehcache配置檔案的路徑,通常是ehcache.xml檔案的路徑。如果沒有指定configlocation,則将使用預設位置的配置檔案建立cachemanager,這是屬于ehcache自身的邏輯,即如果在classpath根路徑下存在ehcache.xml檔案,則直接使用該檔案作為ehcache的配置檔案,否則将使用ehcache-xxx.jar中的ehcache-failsafe.xml檔案作為配置檔案來建立ehcache的cachemanager。此外,如果不希望建立的cachemanager使用預設的名稱(在ehcache.xml檔案中定義的,或者是由cachemanager内部定義的),則可以通過cachemanagername屬性進行指定。下面是一個配置ehcachemanagerfactorybean的示例。

   <!-- 定義cachemanager -->

   <bean id="cachemanager" class="org.springframework.cache.ehcache.ehcachemanagerfactorybean">

      <!-- 指定配置檔案的位置 -->

      <property name="configlocation" value="/web-inf/config/ehcache.xml"/>

      <!-- 指定建立的cachemanager的名稱 -->

      <property name="cachemanagername" value="cachemanagername"/>

       ehcachefactorybean是用來産生ehcache的ehcache對象的factorybean。定義ehcachefactorybean時有兩個很重要的屬性我們可以來指定。一個是cachemanager屬性,其可以指定将用來擷取或建立ehcache的cachemanager對象,若未指定則将通過cachemanager.create()擷取或建立預設的cachemanager。另一個重要屬性是cachename,其表示目前ehcachefactorybean對應的是cachemanager中的哪一個ehcache對象,若未指定預設使用beanname作為cachename。若cachemanager中不存在對應cachename的ehcache對象,則将使用cachemanager建立一個名為cachename的cache對象。此外我們還可以通過ehcachefactorybean的timetoidle、timetolive等屬性指定要建立的cache的對應屬性,注意這些屬性隻對cachemanager中不存在對應cache時建立的cache才起作用,對已經存在的cache将不起作用,更多屬性設定請參考spring的api文檔。此外還有幾個屬性是對不管是已經存在還是新建立的cache都起作用的屬性:statisticsenabled、sampledstatisticsenabled、disabled、blocking和cacheeventlisteners,其中前四個預設都是false,最後一個表示為目前cache指定cacheeventlistener。下面是一個定義ehcachefactorybean的示例。

   <!-- 定義一個ehcache -->

   <bean id="usercache" class="org.springframework.cache.ehcache.ehcachefactorybean">

      <property name="cachename" value="user"/>

      <property name="cachemanager" ref="cachemanager"/>

(注:本文是基于spring3.1.0所寫)