Spring Cache簡(jiǎn)單介紹和使用

概述

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

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

其特點(diǎn)總結(jié)例如以下:

  • 通過少量的配置 annotation 注解就可以使得既有代碼支持緩存
  • 支持開箱即用 Out-Of-The-Box钢坦,即不用安裝和部署額外第三方組件就可以使用緩存
  • 支持 Spring Express Language啥酱,能使用對(duì)象的不論什么屬性或者方法來(lái)定義緩存的 key 和 condition
  • 支持 AspectJ,并通過事實(shí)上現(xiàn)不論什么方法的緩存支持
  • 支持自己定義 key 和自己定義緩存管理者镶殷,具有相當(dāng)?shù)撵`活性和擴(kuò)展性

一、 自己實(shí)現(xiàn)緩存

這里先展示一個(gè)自己定義的緩存實(shí)現(xiàn)绘趋,即不用第三方的組件來(lái)實(shí)現(xiàn)某種對(duì)象的內(nèi)存緩存颤陶。
場(chǎng)景例如以下:

對(duì)一個(gè)賬號(hào)查詢方法做緩存陷遮,賬號(hào)名稱為 key,賬號(hào)對(duì)象為 value帽馋,當(dāng)以同樣的賬號(hào)名稱查詢賬號(hào)的時(shí)候闲坎,直接從緩存中返回結(jié)果茬斧。否則更新緩存。賬號(hào)查詢服務(wù)還支持 reload 緩存(即清空緩存)

首先定義一個(gè)實(shí)體類:賬號(hào)類项秉,具備主要的 id 和 name 屬性。且具備 getter 和 setter 方法

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;
    }

}

然后定義一個(gè)緩存管理器娄蔼,這個(gè)管理器負(fù)責(zé)實(shí)現(xiàn)緩存邏輯底哗,支持對(duì)象的添加、改動(dòng)和刪除锚沸,支持值對(duì)象的泛型。

public class CacheContext<T> {

    private Map<String, T> cache = Maps.newConcurrentMap();

    public T get(String key){
        return  cache.get(key);
    }

    public void addOrUpdateCache(String key,T value) {
        cache.put(key, value);
    }

    // 依據(jù) key 來(lái)刪除緩存中的一條記錄
    public void evictCache(String key) {
        if(cache.containsKey(key)) {
            cache.remove(key);
        }
    }

    // 清空緩存中的全部記錄
    public void evictCache() {
        cache.clear();
    }

}

好哗蜈,如今我們有了實(shí)體類和一個(gè)緩存管理器,還須要一個(gè)提供賬號(hào)查詢的服務(wù)類距潘。此服務(wù)類使用緩存管理器來(lái)支持賬號(hào)查詢緩存炼列。例如以下:

@Service
public class AccountService1 {

    private final Logger logger = LoggerFactory.getLogger(AccountService1.class);

    @Resource
    private CacheContext<Account> accountCacheContext;

    public Account getAccountByName(String accountName) {
        Account result = accountCacheContext.get(accountName);
        if (result != null) {
            logger.info("get from cache... {}", accountName);
            return result;
        }

        Optional<Account> accountOptional = getFromDB(accountName);
        if (!accountOptional.isPresent()) {
            throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
        }

        Account account = accountOptional.get();
        accountCacheContext.addOrUpdateCache(accountName, account);
        return account;
    }

    public void reload() {
        accountCacheContext.evictCache();
    }

    private Optional<Account> getFromDB(String accountName) {
        logger.info("real querying db... {}", accountName);
        //Todo query data from database
        return Optional.fromNullable(new Account(accountName));
    }

}

現(xiàn)在我們寫一個(gè)測(cè)試類俭尖,用于測(cè)試剛才的緩存是否有效

public class AccountService1Test {

    private AccountService1 accountService1;

    private final Logger logger = LoggerFactory.getLogger(AccountService1Test.class);

    @Before
    public void setUp() throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml");
        accountService1 = context.getBean("accountService1", AccountService1.class);
    }

    @Test
    public void testInject(){
        assertNotNull(accountService1);
    }

    @Test
    public void testGetAccountByName() throws Exception {
        accountService1.getAccountByName("accountName");
        accountService1.getAccountByName("accountName");

        accountService1.reload();
        logger.info("after reload ....");

        accountService1.getAccountByName("accountName");
        accountService1.getAccountByName("accountName");
    }
}

依照分析,運(yùn)行結(jié)果應(yīng)該是:首先從數(shù)據(jù)庫(kù)查詢洞翩,然后直接返回緩存中的結(jié)果,重置緩存后骚亿,應(yīng)該先從數(shù)據(jù)庫(kù)查詢。然后返回緩存中的結(jié)果. 查看程序運(yùn)行的日志例如以下:

00:53:17.166 [main] INFO  c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.168 [main] INFO  c.r.s.cache.example1.AccountService - get from cache... accountName
00:53:17.168 [main] INFO  c.r.s.c.example1.AccountServiceTest - after reload ....
00:53:17.168 [main] INFO  c.r.s.cache.example1.AccountService - real querying db... accountName
00:53:17.169 [main] INFO  c.r.s.cache.example1.AccountService - get from cache... accountName

能夠看出我們的緩存起效了循未,可是這樣的自己定義的緩存方案有例如以下劣勢(shì):

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

二镊讼、Spring cache實(shí)現(xiàn)緩存

我們對(duì)AccountService1 進(jìn)行改動(dòng)宽涌。創(chuàng)建AccountService2:

@Service
public class AccountService2 {

    private final Logger logger = LoggerFactory.getLogger(AccountService2.class);

    // 使用了一個(gè)緩存名叫 accountCache
    @Cacheable(value="accountCache")
    public Account getAccountByName(String accountName) {

        // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯蝶棋,直接實(shí)現(xiàn)業(yè)務(wù)
        logger.info("real querying account... {}", accountName);
        Optional<Account> accountOptional = getFromDB(accountName);
        if (!accountOptional.isPresent()) {
            throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
        }

        return accountOptional.get();
    }

    private Optional<Account> getFromDB(String accountName) {
        logger.info("real querying db... {}", accountName);
        //Todo query data from database
        return Optional.fromNullable(new Account(accountName));
    }

}

我們注意到在上面的代碼中有一行:

 @Cacheable(value="accountCache")

這個(gè)注解的意思是,當(dāng)調(diào)用這種方法的時(shí)候玩裙。會(huì)從一個(gè)名叫 accountCache 的緩存中查詢段直,假設(shè)沒有,則運(yùn)行實(shí)際的方法(即查詢數(shù)據(jù)庫(kù))溶诞,并將運(yùn)行的結(jié)果存入緩存中。否則返回緩存中的對(duì)象螺垢。這里的緩存中的 key 就是參數(shù) accountName喧务,value 就是 Account 對(duì)象甩苛。“accountCache”緩存是在 spring*.xml 中定義的名稱。我們還須要一個(gè) spring 的配置文件來(lái)支持基于注解的緩存

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/cache
           http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:component-scan base-package="com.rollenholt.spring.cache"/>

    <context:annotation-config/>

    <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="accountCache"/>
                </bean>
            </set>
        </property>
    </bean>

</beans>

注意這個(gè) spring 配置文件有一個(gè)關(guān)鍵的支持緩存的配置項(xiàng):

<cache:annotation-driven />

這個(gè)配置項(xiàng)缺省使用了一個(gè)名字叫 cacheManager 的緩存管理器讯蒲,這個(gè)緩存管理器有一個(gè) spring 的缺省實(shí)現(xiàn),即 org.springframework.cache.support.SimpleCacheManager墨林。這個(gè)緩存管理器實(shí)現(xiàn)了我們剛剛自己定義的緩存管理器的邏輯,它須要配置一個(gè)屬性 caches旭等,即此緩存管理器管理的緩存集合酌呆,除了缺省的名字叫 default 的緩存搔耕,我們還自己定義了一個(gè)名字叫 accountCache 的緩存,使用了缺省的內(nèi)存存儲(chǔ)方案 ConcurrentMapCacheFactoryBean弃榨,它是基于 java.util.concurrent.ConcurrentHashMap 的一個(gè)內(nèi)存緩存實(shí)現(xiàn)方案菩收。

然后我們編寫測(cè)試程序:

public class AccountService2Test {

    private AccountService2 accountService2;

    private final Logger logger = LoggerFactory.getLogger(AccountService2Test.class);

    @Before
    public void setUp() throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
        accountService2 = context.getBean("accountService2", AccountService2.class);
    }

    @Test
    public void testInject(){
        assertNotNull(accountService2);
    }

    @Test
    public void testGetAccountByName() throws Exception {
        logger.info("first query...");
        accountService2.getAccountByName("accountName");

        logger.info("second query...");
        accountService2.getAccountByName("accountName");
    }
}

以上測(cè)試代碼主要進(jìn)行了兩次查詢鲸睛。第一次應(yīng)該會(huì)查詢數(shù)據(jù)庫(kù),第二次應(yīng)該返回緩存官辈。不再查數(shù)據(jù)庫(kù),我們運(yùn)行一下拳亿∏绻桑看看結(jié)果

01:10:32.435 [main] INFO  c.r.s.c.example2.AccountService2Test - first query...
01:10:32.457 [main] INFO  c.r.s.cache.example2.AccountService2 - real querying db... accountName
01:10:32.458 [main] INFO  c.r.s.c.example2.AccountService2Test - second query...
01:10:32.456 [main] INFO  c.r.s.cache.example2.AccountService2 - real querying db... accountName

能夠看出我們?cè)O(shè)置的基于注解的緩存起作用了肺魁,而在 AccountService.java 的代碼中。我們沒有看到不論什么的緩存邏輯代碼。僅僅有一行注解:@Cacheable(value="accountCache")胡桨,就實(shí)現(xiàn)了主要的緩存方案。

三昧谊、清空緩存

當(dāng)賬號(hào)數(shù)據(jù)發(fā)生變更刽虹,那么必須要清空某個(gè)緩存呢诬,另外還須要定期的清空全部緩存,以保證緩存數(shù)據(jù)的可靠性尚镰。

為了加入清空緩存的邏輯阀圾。我們僅僅要對(duì) AccountService2.java 進(jìn)行改動(dòng)狗唉,從業(yè)務(wù)邏輯的角度上看,它有兩個(gè)須要清空緩存的地方

  • 當(dāng)外部調(diào)用更新了賬號(hào)分俯,則我們須要更新此賬號(hào)相應(yīng)的緩存
  • 當(dāng)外部調(diào)用說(shuō)明又一次載入肾筐,則我們須要清空全部緩存

我們?cè)贏ccountService2的基礎(chǔ)上進(jìn)行改動(dòng)缸剪,改動(dòng)為AccountService3,代碼例如以下:

@Service
public class AccountService3 {

    private final Logger logger = LoggerFactory.getLogger(AccountService3.class);

    // 使用了一個(gè)緩存名叫 accountCache
    @Cacheable(value="accountCache")
    public Account getAccountByName(String accountName) {

        // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯杏节,直接實(shí)現(xiàn)業(yè)務(wù)
        logger.info("real querying account... {}", accountName);
        Optional<Account> accountOptional = getFromDB(accountName);
        if (!accountOptional.isPresent()) {
            throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));
        }

        return accountOptional.get();
    }

    @CacheEvict(value="accountCache",key="#account.getName()")
    public void updateAccount(Account account) {
        updateDB(account);
    }

    @CacheEvict(value="accountCache",allEntries=true)
    public void reload() {
    }

    private void updateDB(Account account) {
        logger.info("real update db...{}", account.getName());
    }

    private Optional<Account> getFromDB(String accountName) {
        logger.info("real querying db... {}", accountName);
        //Todo query data from database
        return Optional.fromNullable(new Account(accountName));
    }
}

我們的測(cè)試代碼例如以下:

public class AccountService3Test {


    private AccountService3 accountService3;

    private final Logger logger = LoggerFactory.getLogger(AccountService3Test.class);

    @Before
    public void setUp() throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
        accountService3 = context.getBean("accountService3", AccountService3.class);
    }

    @Test
    public void testGetAccountByName() throws Exception {

        logger.info("first query.....");
        accountService3.getAccountByName("accountName");

        logger.info("second query....");
        accountService3.getAccountByName("accountName");

    }

    @Test
    public void testUpdateAccount() throws Exception {
        Account account1 = accountService3.getAccountByName("accountName1");
        logger.info(account1.toString());
        Account account2 = accountService3.getAccountByName("accountName2");
        logger.info(account2.toString());

        account2.setId(121212);
        accountService3.updateAccount(account2);

        // account1會(huì)走緩存
        account1 = accountService3.getAccountByName("accountName1");
        logger.info(account1.toString());
        // account2會(huì)查詢db
        account2 = accountService3.getAccountByName("accountName2");
        logger.info(account2.toString());

    }

    @Test
    public void testReload() throws Exception {
        accountService3.reload();
        // 這2行查詢數(shù)據(jù)庫(kù)
        accountService3.getAccountByName("somebody1");
        accountService3.getAccountByName("somebody2");

        // 這兩行走緩存
        accountService3.getAccountByName("somebody1");
        accountService3.getAccountByName("somebody2");
    }
}

在這個(gè)測(cè)試代碼中我們重點(diǎn)關(guān)注testUpdateAccount()方法。在測(cè)試代碼中我們已經(jīng)注釋了在update完account2以后奋渔,再次查詢的時(shí)候。account1會(huì)走緩存卒稳,而account2不會(huì)走緩存,而去查詢db充坑,觀察程序運(yùn)行日志,運(yùn)行日志為:

01:37:34.549 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName1
01:37:34.551 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName1
01:37:34.552 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}
01:37:34.553 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName2
01:37:34.553 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName2
01:37:34.555 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}
01:37:34.555 [main] INFO  c.r.s.cache.example3.AccountService3 - real update db...accountName2
01:37:34.595 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}
01:37:34.596 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName2
01:37:34.596 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName2
01:37:34.596 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}

四捻爷、依照條件操作緩存

前面介紹的緩存方法,不論什么條件份企,都全部對(duì) accountService 對(duì)象的 getAccountByName 方法的調(diào)用都會(huì)啟動(dòng)緩存效果,無(wú)論參數(shù)是什么值。

設(shè)有一個(gè)需求甜紫,就是僅僅有賬號(hào)名稱的長(zhǎng)度小于等于 4 的情況下,才做緩存囚霸,大于 4 的不使用緩存

@CacheEvict源碼:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {

    String[] value() default {};
    String key() default "";
    String keyGenerator() default "";
    String cacheManager() default "";
    String cacheResolver() default "";
    String condition() default "";
    boolean allEntries() default false;
    boolean beforeInvocation() default false;
}

通過查看CacheEvict注解的定義腰根,我們會(huì)發(fā)現(xiàn)定義中有一個(gè)condition描寫敘述:

Spring Expression Language (SpEL) attribute used for conditioning the method caching.Default is "", meaning the method is always cached.

我們能夠利用這種方法來(lái)完畢這個(gè)功能拓型,以下僅僅給出演示樣例代碼:

@Cacheable(value="accountCache",condition="#accountName.length() <= 4")// 緩存名叫 accountCache 
public Account getAccountByName(String accountName) {
    // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
    return getFromDB(accountName);
}

注意當(dāng)中的 condition=”#accountName.length() <=4”劣挫,這里使用了 SpEL 表達(dá)式訪問了參數(shù) accountName 對(duì)象的 length() 方法册养,條件表達(dá)式返回一個(gè)布爾值压固,true/false,當(dāng)條件為 true邓夕。則進(jìn)行緩存操作刘莹,否則直接調(diào)用方法運(yùn)行的返回結(jié)果焚刚。

五、假設(shè)有多個(gè)參數(shù)矿咕,怎樣進(jìn)行 key 的組合

我們要依據(jù)賬號(hào)名抢肛、password對(duì)賬號(hào)對(duì)象進(jìn)行緩存碳柱,而第三個(gè)參數(shù)“是否發(fā)送日志”對(duì)緩存沒有不論什么影響捡絮。所以莲镣,我們能夠利用 SpEL 表達(dá)式對(duì)緩存 key 進(jìn)行設(shè)計(jì)
我們?yōu)锳ccount類添加一個(gè)password 屬性, 然后改動(dòng)AccountService代碼:

 @Cacheable(value="accountCache",key="#accountName.concat(#password)") 
 public Account getAccount(String accountName,String password,boolean sendLog) { 
   // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯瑞侮。直接實(shí)現(xiàn)業(yè)務(wù)
   return getFromDB(accountName,password); 
 }

注意上面的 key 屬性的圆,當(dāng)中引用了方法的兩個(gè)參數(shù) accountName 和 password半火,而 sendLog 屬性沒有考慮。由于其對(duì)緩存沒有影響钮糖。

accountService.getAccount("accountName", "123456", true);// 查詢數(shù)據(jù)庫(kù)
accountService.getAccount("accountName", "123456", true);// 走緩存
accountService.getAccount("accountName", "123456", false);// 走緩存
accountService.getAccount("accountName", "654321", true);// 查詢數(shù)據(jù)庫(kù)
accountService.getAccount("accountName", "654321", true);// 走緩存

六梅掠、怎樣做到:既要保證方法被調(diào)用。又希望結(jié)果被緩存

依據(jù)前面的樣例酪我,我們知道,假設(shè)使用了 @Cacheable 注解挠蛉,則當(dāng)反復(fù)使用同樣參數(shù)調(diào)用方法的時(shí)候,方法本身不會(huì)被調(diào)用運(yùn)行谴古。即方法本身被略過了质涛,取而代之的是方法的結(jié)果直接從緩存中找到并返回了掰担。

現(xiàn)實(shí)中并不總是如此,有些情況下我們希望方法一定會(huì)被調(diào)用带饱,由于其除了返回一個(gè)結(jié)果毡代,還做了其它事情勺疼。比如記錄日志教寂。調(diào)用接口等执庐。這個(gè)時(shí)候。我們能夠用 @CachePut 注解轨淌,這個(gè)注解能夠確保方法被運(yùn)行迂烁,同一時(shí)候方法的返回值也被記錄到緩存中递鹉。

@Cacheable(value="accountCache")
 public Account getAccountByName(String accountName) { 
   // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
   return getFromDB(accountName); 
 } 

 // 更新 accountCache 緩存
 @CachePut(value="accountCache",key="#account.getName()")
 public Account updateAccount(Account account) { 
   return updateDB(account); 
 } 
 private Account updateDB(Account account) { 
   logger.info("real updating db..."+account.getName()); 
   return account; 
 }

測(cè)試代碼例如以下:

Account account = accountService.getAccountByName("someone"); 
account.setPassword("123"); 
accountService.updateAccount(account); 
account.setPassword("321"); 
accountService.updateAccount(account); 
account = accountService.getAccountByName("someone"); 
logger.info(account.getPassword()); 

如上面的代碼所看到的躏结。我們首先用 getAccountByName 方法查詢一個(gè)人 someone 的賬號(hào)却盘。這個(gè)時(shí)候會(huì)查詢數(shù)據(jù)庫(kù)一次媳拴。可是也記錄到緩存中了禀挫。然后我們改動(dòng)了password旬陡,調(diào)用了 updateAccount 方法语婴。這個(gè)時(shí)候會(huì)運(yùn)行數(shù)據(jù)庫(kù)的更新操作且記錄到緩存,我們?cè)俅胃膭?dòng)password并調(diào)用 updateAccount 方法。然后通過 getAccountByName 方法查詢匿醒,這個(gè)時(shí)候。由于緩存中已經(jīng)有數(shù)據(jù)缠导,所以不會(huì)查詢數(shù)據(jù)庫(kù),而是直接返回最新的數(shù)據(jù)僻造,所以打印的password應(yīng)該是“321”

七憋他、@Cacheable髓削、@CachePut、@CacheEvict 凝視介紹

1.@Cacheable執(zhí)行緩存

@Cacheable可用于修飾類或修飾方法立膛,當(dāng)使用@Cacheable修飾類時(shí)揪罕,用于告訴Spring在類級(jí)別上進(jìn)行緩存———程序調(diào)用該類的實(shí)例的任何方法時(shí)都需要緩存宝泵,而且共享同一個(gè)緩存區(qū);當(dāng)使用@Cacheable修飾方法時(shí)儿奶,用于告訴Spring在方法級(jí)別上進(jìn)行緩存———只有當(dāng)程序調(diào)用該方法時(shí)才需要緩存

類級(jí)別的緩存:
使用@Cacheable修飾類時(shí)框往,就可控制Spring在類級(jí)別進(jìn)行緩存廓握,這樣當(dāng)程序調(diào)用該類的任意方法時(shí),只要傳入的參數(shù)相同隙券,Spring就會(huì)使用緩存

@Service("userService")
// 指定將數(shù)據(jù)放入users緩存區(qū)
@Cacheable(value = "users")
public class UserServiceImpl implements UserService
{
    public User getUsersByNameAndAge(String name, int age)
    {
        System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
        return new User(name, age);
    }
    public User getAnotherUser(String name, int age)
    {
        System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
        return new User(name, age);
    }
}

當(dāng)程序第一次調(diào)用該類的實(shí)例的某個(gè)方法時(shí)男应,Spring緩存機(jī)制會(huì)將該方法返回的數(shù)據(jù)放入指定緩存區(qū)娱仔。以后程序調(diào)用該類的實(shí)例的任何方法時(shí),只要傳入的參數(shù)相同牲迫,Spring將不會(huì)真正執(zhí)行該方法耐朴,而是直接利用緩存區(qū)中的數(shù)據(jù)

使用@Cacheable是可指定如下屬性:

  • value:必須屬性盹憎。該屬性可指定多個(gè)緩存區(qū)的名字,用于指定將方法返回值放入指定的緩存區(qū)內(nèi)
  • key:通過SpEL表達(dá)式顯式指定緩存的key
  • condition:該屬性指定一個(gè)返回boolean值的SpEL表達(dá)式陪每,只有當(dāng)該表達(dá)式返回true時(shí)影晓,Spring才會(huì)緩存方法返回值
  • unless:該屬性指定一個(gè)返回boolean值的SpEL表達(dá)式,當(dāng)該表達(dá)式返回true時(shí)挂签,Spring就不緩存方法返回值

與@Cache注解功能類似的還有一個(gè)@Cacheput注解疤祭,與@Cacheable不同的是饵婆,@Cacheput修飾的方法不會(huì)讀取緩存區(qū)中的數(shù)據(jù)———這意味著不管緩存區(qū)是否已有數(shù)據(jù),@Cacheput總會(huì)告訴Spring要重新執(zhí)行這些方法侨核,并在此將方法返回值放入緩存區(qū)

方法級(jí)別的緩存:
使用@Cacheable修飾方法時(shí)草穆,就可控制Spring在方法級(jí)別進(jìn)行緩存搓译,這樣當(dāng)程序調(diào)用該方法時(shí)续挟,只要傳入的參數(shù)相同侥衬,Spring就會(huì)使用緩存

@Service("userService")
public class UserServiceImpl implements UserService
{
    @Cacheable(value = "users1")
    public User getUsersByNameAndAge(String name, int age)
    {
        System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
        return new User(name, age);
    }
    @Cacheable(value = "users2")
    public User getAnotherUser(String name, int age)
    {
        System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
        return new User(name, age);
    }
}

上面代碼中指定getUsersByNameAndAge()和getAnotherUser()方法分別使用不同的緩存區(qū),這意味著兩個(gè)方法都會(huì)緩存轴总,但由于它們使用了不同的緩存區(qū)直颅,因此它們不能共享緩存區(qū)數(shù)據(jù)

2.使用@CacheEvict清除緩存

被@CacheEvict注解修飾的方法可用于清除緩存怀樟,使用@CacheEvict注解時(shí)可指定如下屬性:

  • value:必需屬性。用于指定該方法用于清除哪個(gè)緩存區(qū)的數(shù)據(jù)
  • key:通過SpEL表達(dá)式顯式指定緩存的key
  • allEntries:該屬性指定是否清空整個(gè)緩存區(qū)
  • beforeInvocation:該屬性指定是否在執(zhí)行方法之前清除緩存往堡。默認(rèn)實(shí)在false
  • condion:該屬性指定一個(gè)SpEL變道時(shí)械荷,只有當(dāng)該表達(dá)式為true時(shí)才清除緩存
@Service("userService")
@Cacheable(value = "users")
public class UserServiceImpl implements UserService
{
    public User getUsersByNameAndAge(String name, int age)
    {
        System.out.println("--正在執(zhí)行findUsersByNameAndAge()查詢方法--");
        return new User(name, age);
    }
    public User getAnotherUser(String name, int age)
    {
        System.out.println("--正在執(zhí)行findAnotherUser()查詢方法--");
        return new User(name, age);
    }
    // 指定根據(jù)name虑灰、age參數(shù)清除緩存
    @CacheEvict(value = "users")
    public void evictUser(String name, int age)
    {
        System.out.println("--正在清空"+ name
            + " , " + age + "對(duì)應(yīng)的緩存--");
    }
    // 指定清除user緩存區(qū)所有緩存數(shù)據(jù)
    @CacheEvict(value = "users" , allEntries=true)
    public void evictAll()
    {
        System.out.println("--正在清空整個(gè)緩存--");
    }
}
3.基本原理

一句話介紹就是Spring AOP的動(dòng)態(tài)代理技術(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穆咐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子对湃,更是在濱河造成了極大的恐慌崖叫,老刑警劉巖拍柒,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拆讯,居然都是意外死亡脂男,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門疆液,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人陕贮,你說(shuō)我怎么就攤上這事“怪” “怎么了掉缺?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵戈擒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我筐高,道長(zhǎng)搜囱,這世上最難降的妖魔是什么柑土? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任蜀肘,我火速辦了婚禮稽屏,結(jié)果婚禮上扮宠,老公的妹妹穿的比我還像新娘。我一直安慰自己坛增,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布薄腻。 她就那樣靜靜地躺著收捣,像睡著了一般庵楷。 火紅的嫁衣襯著肌膚如雪坏晦。 梳的紋絲不亂的頭發(fā)上嫁乘,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天昆婿,我揣著相機(jī)與錄音蜓斧,去河邊找鬼仓蛆。 笑死挎春,一個(gè)胖子當(dāng)著我的面吹牛看疙,可吹牛的內(nèi)容都是我干的豆拨。 我是一名探鬼主播能庆,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼施禾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搁胆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起渠旁,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤攀例,失蹤者是張志新(化名)和其女友劉穎顾腊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杂靶,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梆惯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年吗垮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抱既。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡职烧,死狀恐怖防泵,靈堂內(nèi)的尸體忽然破棺而出蚀之,到底是詐尸還是另有隱情捷泞,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布锁右,位于F島的核電站失受,受9級(jí)特大地震影響咏瑟,放射性物質(zhì)發(fā)生泄漏拂到。R本人自食惡果不足惜码泞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望余寥。 院中可真熱鬧领铐,春花似錦、人聲如沸绪撵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幻碱。三九已至改艇,卻和暖如春坟岔,著一層夾襖步出監(jiān)牢的瞬間谒兄,已是汗流浹背社付。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸥咖,地道東北人燕鸽。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓啼辣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鸥拧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子党远,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容