Spring 快取

俺就不起網名發表於2018-09-13

目錄

一、相關注解

二、案例程式碼

三、鍵的生成策略

四、自定義快取

五、總結


簡單介紹使用Spring框架Cacheable

一、相關注解

1、@Cacheable註解

可以標記在一個方法上,也可以標記在一個類上。@Cacheable可以指定三個屬性,value、key和condition。

value屬性是必須指定的,其表示當前方法的返回值是會被快取在哪個Cache上的,對應Cache的名稱。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個陣列。

key屬性是用來指定Spring快取方法的返回結果時對應的key的。該屬性支援SpringEL表示式。當我們沒有指定該屬性時,Spring將使用預設策略生成key。

condition屬性預設為空,表示將快取所有的呼叫情形。其值是通過SpringEL表示式來指定的,當為true時表示進行快取處理;當為false時表示不進行快取處理,即每次呼叫該方法時該方法都會執行一次。如下示例表示只有當user的id為偶數時才會進行快取。如 @Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")

2、@CachePut

@CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。

3、@CacheEvict

@CacheEvict是用來標註在需要清除快取元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發快取的清除操作。

@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用預設策略生成的key;condition表示清除操作發生的條件。

二、案例程式碼

1、service層程式碼

@Service
public class PersonService {

    @Cacheable(value = "personCache")
    public Person getPersonByName(String name) {
        // 方法內部實現不考慮快取邏輯,直接實現業務
        System.out.println("呼叫了Service方法");
        return getFromDB(name);
    }

    private Person getFromDB(String name) {
        System.out.println("從資料庫查詢了");
        return new Person();
    }
}

2、配置CacheManager

    <context:component-scan base-package="demo06"/>
    <cache:annotation-driven />
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="default"/>
                </bean>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="personCache"/>
                </bean>
            </set>
        </property>
    </bean>

<cache:annotation-driven />,支援快取的配置項,這個配置項預設使用了一個名字叫 cacheManager 的快取管理器,這個快取管理器有一個 spring 的預設實現,即org.springframework.cache.support.SimpleCacheManager。這個快取管理器實現了我們剛剛自己定義的快取管理器的邏輯,它須要配置一個屬性 caches,即此快取管理器管理的快取集合,除了預設的名字叫 default 的快取,我們還自己定義了一個名字叫 personCache 的快取,使用了預設的記憶體儲存方案 ConcurrentMapCacheFactoryBean,它是基於 java.util.concurrent.ConcurrentHashMap 的一個記憶體快取實現方案。

<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等快取註解。

3、測試程式碼

public class PersonCacheTest {

    private PersonService personService;

    @Before
    public void setUp() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans/application_cache.xml");
        personService = context.getBean("personService", PersonService.class);
    }

    @Test
    public void testGetPersonByName() {
        System.out.println("第一次查詢張三………………");
        personService.getPersonByName("張三");
        System.out.println("第二次查詢李四………………");
        personService.getPersonByName("李四");
        System.out.println("第三次查詢張三………………");
        personService.getPersonByName("張三");
    }
}

輸出結果:

第一次查詢張三………………
呼叫了Service方法
從資料庫查詢了
第二次查詢李四………………
呼叫了Service方法
從資料庫查詢了
第三次查詢張三………………

從上可以看出,當引數相同時,第二次呼叫時直接從快取中獲取結果了,service層都沒有呼叫了。

三、鍵的生成策略

鍵的生成策略有兩種,一種是預設策略,一種是自定義策略。比如上面的案例就是使用了預設策略。

1、預設策略

預設的key生成策略是通過KeyGenerator生成的,其預設策略如下:

a、如果方法沒有引數,則使用0作為key。
b、如果只有一個引數的話則使用該引數作為key。
c、如果引數多與一個的話則使用所有引數的hashCode作為key。
d、如果我們需要指定自己的預設策略的話,那麼我們可以實現KeyGenerator介面,定義生成key的方法,然後指定我們的Spring Cache使用的KeyGenerator為我們自己定義的KeyGenerator。如下

<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
<cache:annotation-driven key-generator="userKeyGenerator"/>

2、自定義策略

自定義策略是指我們可以通過Spring的EL表示式來指定我們的key。這裡的EL表示式可以使用方法引數及它們對應的屬性。使用方法引數時我們可以直接使用“#引數名”或者“#p引數index”。如下:

@Cacheable(value="users", key="#id")
public User find(Integer id) {}

@Cacheable(value="users", key="#user.id")
public User find(User user) {}

四、自定義快取

1、首先須要自己實現 Cache 介面。Cache 介面負責實際的快取邏輯。比如新增鍵值對、儲存、查詢和清空等。利用 Cache 介面,我們能夠對接不論什麼第三方的快取系統。比如 EHCache、OSCache,甚至一些記憶體資料庫比如 memcache 或者 redis 等。

public class MyCache implements Cache {

    private String name;
    private Map<String, Person> store = new HashMap<String, Person>();

    public void setName(String name) {
        this.name = name;
    }

    public MyCache() {
    }

    public MyCache(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Object getNativeCache() {
        return store;
    }

    @Override
    public ValueWrapper get(Object key) {
        ValueWrapper result = null;
        Person person = store.get(key);
        if (person != null) {
            result = new SimpleValueWrapper(person);
        }
        return result;
    }

    @Override
    public <T> T get(Object o, Class<T> aClass) {
        return null;
    }

    @Override
    public void put(Object key, Object value) {
        Person person = (Person) value;
        store.put((String) key, person);
    }

    @Override
    public void evict(Object key) {
        if (store.containsKey(key)) {
            store.remove(key);
        }
    }

    @Override
    public void clear() {
        store.clear();
    }
}

2、其次,我們須要提供一個 CacheManager 介面的實現,這個介面告訴 spring 有哪些 cache 例項,spring 會依據 cache 的名字查詢 cache 的例項。

public class MyCacheManager extends AbstractCacheManager {
    private Collection<? extends MyCache> caches;

    @Override
    protected Collection<? extends Cache> loadCaches() {
        return this.caches;
    }

    public void setCaches(Collection<? extends MyCache> caches) {
        this.caches = caches;
    }
}

3、cacheManage配置,caches屬性值賦值

<context:component-scan base-package="demo06"/>
<cache:annotation-driven /> 
<bean id="cacheManager" class="demo06.MyCacheManager">
    <property name="caches"> 
    	<set> 
        	<bean class="demo06.MyCache"> 
                <property name="name" value="personCache"/>
            </bean>
       </set> 
    </property> 
</bean> 

執行上面的測試類 PersonCacheTest,可以看到輸出一樣。

先呼叫了Cache的get方法查詢是否有快取,如果有的話直接從快取返回,如果沒有則呼叫service之後再put方法放入快取。

五、總結

1、spring快取的常用註解:@Cacheable、@CachePut、@CacheEvict

2、<cache:annotation-driven/>及cacheManage 

3、快取的鍵生成策略,預設的key生成策略是通過KeyGenerator生成的,我們也可以自己實現該介面;我們也可以通過el表示式來自定義key;

4、自定義快取三步驟:實現Cache介面的增刪改查、實現CacheManage介面對cache(或caches進行管理)、XML檔案配置cacheManage;

相關文章