背景

在以往开发中,更多的是引入Redis,直接封装Redis缓存工具类使用,但是这种引入方式,并不利于后期项目的拓展,如果在想替换一个缓存方式或者使用多级缓存方式的时候,改动起来就比较麻烦,需要替换的东西也会比较多,并不是很灵活。
而在Spring Boot 2.x中引入了多Cache支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 两个接口来统一不同的缓存技术。Cache 接口包含缓存的常用操作:增加、删除、读取等。CacheManager 是 Spring 各种缓存的抽象接口。
Spring 支持的常用 CacheManager 如下:

CacheManager

描述

SimpleCacheManager

使用简单的 Collection 来存储缓存

ConcurrentMapCacheManager

使用 java.util.ConcurrentHashMap 来实现缓存

EhCacheCacheManger

使用EhCache作为缓存技术。EhCache 是一个纯 Java 的进程内缓存框架,特点快速、精干,是 Hibernate 中默认的 CacheProvider,也是 Java 领域应用最为广泛的缓存

CaffeineCacheManager

使用 Caffeine 作为缓存技术。用于取代 Guava 缓存技术。

RedisCacheManager

使用Redis作为缓存技术

HazelcastCacheManager

使用Hazelcast作为缓存技术

CompositeCacheManager

用于组合 CacheManager,可以从多个 CacheManager 中轮询得到相应的缓存

Spring Cache 提供了 @Cacheable 、@CachePut 、@CacheEvict 、@Caching 等注解,在方法上使用,来实现数据的缓存、获取、和清除。

代码片段说明

此次项目开发主要是用Cache+Redis实现缓存。

  • 引入依赖包
    分别需要将Redis和Cache引入。
  1. gradle
implementation "org.springframework.boot:spring-boot-starter-cache"
implementation "org.springframework.boot:spring-boot-starter-data-redis"
  1. maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 创建RedisCacheConfig实现CacheManager
import java.lang.reflect.Method;
import java.time.Duration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching  //开起缓存
public class RedisCacheConfig {

  /**
   * 重新定义KeyGenerator.
   */
  @Bean
  public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
      @Override
      public Object generate(Object target, Method method, Object... params) {
        StringBuilder sb = new StringBuilder();
        sb.append(target.getClass().getName());
        sb.append(method.getName());
        for (Object obj : params) {
          sb.append(obj.toString());
        }
        return sb.toString();
      }
    };
  }

  @Bean
  CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheWriter redisCacheWriter = RedisCacheWriter
            .nonLockingRedisCacheWriter(connectionFactory);
    RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
    defaultCacheConfig.entryTtl(Duration.ofSeconds(600));  //秒
    RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    return cacheManager;
  }

  /**
   * 定义Redis存储模板.
   */
  @Bean
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());
    return redisTemplate;
  }

}
  • 在.yml配置文件中配置Cache.type及Redis配置信息
spring:
  cache:
    type: redis
  redis:
    open: true  # 是否开启redis缓存  true开启   false关闭
    database: 0
    host: ${DB_HOST:localhost}
    port: ${DB_PORT:6379}
    password:    # 密码(默认为空)
    jedis:
      pool:
        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10      # 连接池中的最大空闲连接
        min-idle: 5       # 连接池中的最小空闲连接
  • 测试验证
  1. 创建一个测试实体User
@Entity
@Table(name = "users")
public class User implements Serializable {
  private static final Long serialVersionUID = 1L;

  @Id
  private Long id;

  private String name;

  private Date createdAt;

  private Date updatedAt;
}

上面需要注意的是,如果一个对象需要插入到缓存中,那么这个对象是需要序列化的,也就是需要继承Serializable。否则是无法成功插入到Cache缓存中的。

  1. 创建一个实现类,实现方法中使用注解来测试是否可以使用RedisCache。
import java.util.Optional;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {

  @Override
  //key值这里只是测试用,详细可以参考spring boot 文档,或google
  @Cacheable(value = "users", key = "'1'", unless = "#result == null")
  Optional<User> findById(Long id);

  @Override
  @CachePut(value = "users", key = "'1'")
  <S extends User> S save(S user);
}

value指定的是cacheNames,声明指定一个对应的cache空间,可以按照之前直接使用redis的key值一样理解。
这里的key指定的是在这个cache空间中的主键,比如上面User对象中,可以指定id属性为key,不能使用可能重复的属性来指定key建,否则会重叠;获取的时候也是直接指定的id来查询缓存中是否存在,如果存在直接返回缓存中的数据,不存在才会执行下面的方法,并将结果加入缓存并返回。

  • @Cacheable
    对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
  • @CachePut
    @CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

注意,@CachePut使用主要针对的是SaveOrUpdate,并且需要使用注解的方法是需要有对应返回值的,如上面代码片段中的update方法,是需要将对应User返回的,否则执行完成后没有返回值会将对应key建的值置为null。

  • @CacheEvict
    @CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key。
  1. 创建一个测试接口会测试类测试上面插入或更新缓存内数据是否正确。
完结