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、参考

相关文章
相关标签/搜索