Spring Cache 筆記

1、緩存介紹

在 Java 中,通常對調用方法進行緩存控制,好比調用 findUserById(id),那麼應該在調用此方法以前先從緩存中查找,若是沒有再掉該方法如從數據庫中加載,接着添加到緩存中,下次調用時將會從緩存中獲取到數據。php

自 Spring 3.1 起,提供了相似於 @Transactional 註解事務的註解 Cache 支持,且提供了 Cache 抽象;使用Spring Cache 的好處:html

  • 提供基本的 Cache 抽象,方便切換各類底層 Cache;
  • 經過註解 Cache 能夠實現相似於事務同樣,緩存邏輯透明的應用到業務代碼上,且只須要更少的代碼就能夠完成;
  • 提供事務回滾時也自動回滾緩存;
  • 支持比較複雜的緩存邏輯;

對於 Spring Cache 抽象,主要從如下幾個方面學習:java

  • 基於 xml 和 註解的配置示例
  • 與 Redis 結合的緩存配置示例
  • 實現複雜的 Cache 邏輯

本文所使用的框架版本:redis

  • Spring:4.1.0.RELEASE
  • Spring Data Redis:1.4.0.RELEASE
  • Spring Boot:2.1.3.RELEASE

2、基本配置

使用 JVM 內存做爲緩存 Provider

Xml 配置

<!-- 須要緩存的對象 -->
<bean id="bookService" class="com.ariclee.cache.xml.XmlBookService"/>

<!-- 聲明緩存 -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager" >
    <cache:caching cache="books">
        <!-- 讀取緩存: 應用到讀取數據的方法上,便可緩存的方法,如查找方法, 先從緩存中讀取,若是沒有再調用方法獲取數據, 而後把數據添加到緩存中 -->
        <cache:cacheable method="findBook" key="#id"/>

        <!-- 更新緩存: 應用到寫數據的方法上,如新增/修改方法 調用方法時會自動把相應的數據放入緩存 -->
        <cache:cache-put method="saveBook" key="#book.id"/>

        <!-- 刪除單個緩存: 應用到移除數據的方法上,如刪除方法 調用方法時會從緩存中移除相應的數據 -->
        <cache:cache-evict method="delete" key="id"/>

        <!-- 清空緩存:同上,注意屬性 all-entries 默認爲 false -->
        <cache:cache-evict method="deleteAll" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!-- 緩存切面 -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.ariclee.cache.xml.XmlBookService.*(..))"/>
</aop:config>

<!-- 方式一:註冊緩存管理(JVM 緩存) -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

<!-- 方式二:註冊緩存管理(JVM 緩存) -->
<bean id="cacheManager2" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
    <constructor-arg value="books"/>
</bean>
複製代碼

聲明須要緩存的類,聲明緩存並配置,並使用名爲 books 的緩存,聲明方法 findBook 須要緩存,並使用 id 爲鍵。註冊 cacheManager 類,並使用 JVM 內存做爲實際緩存。spring

註解配置

使用 xml 開啓包掃描數據庫

<mvc:annotation-driven/>

<context:component-scan base-package="com.ariclee.cache.annotation" >
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<context:component-scan base-package="com.ariclee.cache.annotation" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

複製代碼

使用 @Cacheable 註解,做用在 findBook 方法上,value 屬性值賦爲 books 意爲緩存的名字。express

@Component
public class AnnotationBookService {
    private static Map<String, Book> respository = new HashMap<>();

    static {
        respository.put("1", new Book("Thinking In Java"));
        respository.put("2", new Book("EfficetiveJava"));
    }

    // 同 xml
    @Cacheable(value = "books")
    public Book findBook(String id) {
        return respository.get(id);
    }
    
    // 同 xml
    @CachePut(value = "books", key = "#book.id")
    public void saveBook(Book book) {
        respository.put(book.getId(), book);
    }

    // 同 xml
    @CacheEvict(value = "books", key = "id", allEntries = false)
    public void delete(String id) {
        respository.remove(id);
    }

    // 同 xml
    @CacheEvict(value = "books", allEntries = true)
    public void deleteAll() {
        respository.clear();
    }
}
複製代碼

使用註解和類配置緩存管理類,ConcurrentMapCacheManager 爲 spring 提供的在內存中管理緩存的簡單類。緩存

@EnableCaching
@Configuration
public class JvmAnnotationCacheConfigure implements CachingConfigurer {
    @Bean
    @Override
    public CacheManager cacheManager() {
        // 使用 JVM 內存
       return new ConcurrentMapCacheManager("books");
    }
     // 省略...
}
複製代碼

SptingBoot 配置

@SpringBootApplication
@EnableCaching
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}
複製代碼

使用 @EnableCaching 註解開啓緩存, yml 配置文件以下:mvc

spring:
 cache:
 type: simple
 cache-names: books
複製代碼

其中 type 值爲 org.springframework.boot.autoconfigure.cache.CacheType 類屬性。app

使用 Redis 做爲緩存 Provider

須要引入 Redis 客戶端依賴,pom:

<!-- redis client -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.5.2</version>
</dependency>
複製代碼

Xml 配置

application.xml

<!-- 註冊緩存管理(Redis) -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="127.0.0.1"/>
    <property name="port" value="6379"/>
    <property name="password" value="2940184"/>
    <property name="timeout" value="2000"/>
</bean>

<!-- 聲明 RedisTemplate 類-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
</bean>

<!-- 聲明緩存管理類 -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    <constructor-arg ref="redisTemplate" />
    <constructor-arg value="books" />
</bean>
複製代碼

聲明 Redis 鏈接工廠和 RedisTemplate。聲明緩存管理類,注入 redisTemplate,並設置緩存名爲 books。

註解配置

建立 Redis 配置類

@Configuration
public class RedisConfigure {
    // 聲明 redis 連接工廠類
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName("127.0.0.1");
        factory.setPassword("123456");
        factory.setPort(6379);
        factory.setTimeout(2000);
        factory.setUsePool(false);
        factory.setPoolConfig(new JedisPoolConfig());
        factory.setDatabase(1);
        factory.setConvertPipelineAndTxResults(false);
        return factory;
    }

    // 聲明 redis 操做模板類
    @Bean
    public RedisTemplate redisTemplate() {
        RedisTemplate temp = new RedisTemplate();
        temp.setConnectionFactory(this.jedisConnectionFactory());
        return temp;
    }
}
複製代碼

建立 Cache 配置類

@EnableCaching
@Configuration
public class RedisAnnotationCacheConfigure implements CachingConfigurer {
    @Autowired
    RedisTemplate redisTemplate;

    @Bean
    @Override
    public CacheManager cacheManager() {
        // 使用 Redis
        return new RedisCacheManager(redisTemplate, 
                                    Collections.singletonList("books"));
    }
    // 省略...
}
複製代碼

SpringBoot 配置

spring:
  cache:
    cache-names: books
    type: redis
    redis:
      use-key-prefix: true
  redis:
    host: 127.0.0.1
    port: 6379
    password: 2940184
    timeout: 2000s
    database: 1
複製代碼

使用 EhCache 做爲緩存 Provider

須要引入 ehcache 緩存依賴,pom:

<!-- ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.8</version>
</dependency>
複製代碼

xml 配置

application.xml

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:ehcache.xml"/>
</bean>

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehcache"/>
</bean>
複製代碼

ehcache.xml(從 www.ehcache.org/ehcache.xml 拷貝)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">

    <defaultCache maxEntriesLocalHeap="0" eternal="false" timeToIdleSeconds="1200" timeToLiveSeconds="1200">
      <!--<terracotta/>-->
    </defaultCache>

    <cache name="books" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>
</ehcache>
複製代碼

註解配置

@EnableCaching
@Configuration
public class EhCacheAnnotationCacheConfigure implements CachingConfigurer {

    @Bean
    @Override
    public CacheManager cacheManager() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        ClassPathResource resource = new ClassPathResource("ehcache.xml");
        factoryBean.setConfigLocation(resource);
        try {
            factoryBean.afterPropertiesSet();
        } catch (Exception e){
            e.printStackTrace();
        }

        EhCacheCacheManager manager = new EhCacheCacheManager();
        manager.setCacheManager(factoryBean.getObject());
        return manager;
    }
    // 省略
}
複製代碼

SpringBoot 配置

pom:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.4</version>
</dependency>
複製代碼

在 application.yml 中將 type 修改成 ehcache,並將 ehcache.xml(同上)放到 resources 文件夾下:

spring:
 cache:
 cache-names: books
 type: ehcache
 ehcache:
 config: classpath:ehcache.xml
複製代碼

3、帶邏輯的緩存實現

條件緩存

xml 方式:

<!-- 只緩存參數名中包含 Java -->
<cache:cacheable method="findBookByName" condition="#name.contains('Java')" key="#name"/>

<!-- 不緩存結果集中帶有 default -->
<cache:cacheable method="findBookByName" unless="#result.name.contains('default')" key="#name"/>
複製代碼

註解方式:

// 同 xml
@Cacheable(value = "books", condition = "#name.contains('Java')")
public Book findBookByName(String name) {}

// 同 xml
@Cacheable(value = "books", unless = "#result.name.contains('default')")
public Book findBookByName(String name) {}
複製代碼

condition 屬性搭配在 Cacheable 註解時,表示 」當 condition 爲 true 讀緩存「,做用於入參,unless 屬性表示 "當 unless 爲 true 不寫緩存",做用於返回值。注意加以區別!屬性 conditionunless 還能夠做用於 CachePut 註解:

// 當 id 爲 10 時,寫入緩存
@CachePut(value = "books", key = "#book.id", condition = "#book.id == 10")
public void saveBook(Book book) {}

// 當 id 爲 10 時,不寫入緩存
@CachePut(value = "books", key = "#book.id", unless = "#book.id == 10")
public void saveBook(Book book) {}
複製代碼

組合操做

好比新增書籍成功後,咱們要添加 id:book;name:book;的緩存鍵值對。此時就須要組合多個註解標籤:

xml 方式:

<cache:caching method="saveAndAddAllCacheMapping" cache="books">
    <cache:cache-put key="#book.id"/>
    <cache:cache-put key="#book.name"/>
</cache:caching>
複製代碼

註解方式:

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"),
    }
)
public Book saveBook(Book book) {}
複製代碼

再如根據書籍名字查找時,在第一次放入緩存的時候,將 id:book 的緩存映射也加進去,則能夠經過如此組合註解實現:

@Caching(  
        cacheable = {  
                @Cacheable(value = "books", key = "#name")  
        },  
        put = {  
                @CachePut(value = "books", key = "#result.id",
                @CachePut(value = "books", key = "#result.name")  
        }  
) 
public Book findBookByName(String name) {}
複製代碼

SpEL

SpEL 官方文檔

名字 位置 描述 示例
methodName root對象 當前被調用的方法名 #root.methodName
method root對象 當前被調用的方法 #root.method.name
target root對象 當前被調用的目標對象 #root.target
targetClass root對象 當前被調用的目標對象類 #root.targetClass
args root對象 當前被調用的方法的參數列表 #root.args[0]
caches root對象 當前方法調用使用的緩存列表(如 @Cacheable(value={"cache1", "cache2"})),則有兩個 cache #root.caches[0].name
argument name 執行上下文 當前被調用的方法的參數,如 findById(Long id) ,咱們能夠經過 #id 拿到參數 #user.id
result 執行上下文 方法執行後的返回值(僅當方法執行以後的判斷有效,如‘unless’,'cache evict'的beforeInvocation=false) #result

4、過時控制

Directly through your cache provider. The cache abstraction is… well, an abstraction not a cache implementation.

Spring Cache 是抽象框架沒有提供過時控制的功能,過時實現交給緩存功能提供者。如 org.springframework.data.redis.cache.RedisCacheManager 類提供 Map<String, Long> expires 屬性能夠設置指定名字的緩存過時時間。

5、自定義緩存鍵

須要實現 org.springframework.cache.interceptor.KeyGenerator 接口,複寫 generate 方法。如:

public class CustomKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        String key = this.generateKey(params);
        System.out.println("生成緩存鍵:" + key);
        return key;
    }

    private String generateKey(Object[] params) {
        if (params == null || params.length < 1) {
            return "";
        }
        StringJoiner stringJoiner = new StringJoiner(":");
        for (Object obj : params) {
            if (obj instanceof Book) {
                Book temp = (Book) obj;
                stringJoiner.add(temp.getId() + ":" + temp.getName());
            }
            else {
                stringJoiner.add(obj.toString());
            }
        }
        return stringJoiner.toString();
    }
}
複製代碼

6、自定義緩存註解

使用組合註解

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"),
    }
)
public Book saveBook(Book book) {}
複製代碼

聲明一個組合註解

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"),
    }
)  
@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
public @interface BookCaching {  
} 
複製代碼

優化後使用

@BookCaching
public Book saveBook(Book book) {}
複製代碼

7、自定義緩存管理類

參見 org.springframework.cache.concurrent.ConcurrentMapCacheManager

8、參考

相關文章
相關標籤/搜索