玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(中)

简介: 玩转Spring Cache --- 整合分布式缓存Redis Cache(使用Lettuce、使用Spring Data Redis)【享学Spring】(中)

Redis和Spring Cache整合


Redis和Spring Cache整合,让能通过缓存注解优雅的操作Redis是本文的主菜。


因为Redis分布式缓存它是client/server模式,所以它的整合和前面整合Ehcache等还是有些不一样的地方的 。但在有了上篇文章做铺垫,加上上面介绍Spring Data Redis的使用之后,要集成它也是易如反掌之事。


RedisCacheManager

老规矩,整合前先看看Redis对CacheManager接口的实现RedisCacheManager:


说明:Spring Data Redis2.x对此类进行了大幅的重写,除了类名没变,内容完全重写了。

此处我给出这张对比图,希望小伙伴们也能做到心中有数。(本文以2.x版本为例)


image.png


// @since 2.0  这里面用到了2.0提供的 RedisCacheConfiguration和RedisCacheWriter等
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
  // 这两个哥们都是不能为null的~~~
  private final RedisCacheWriter cacheWriter;
  // 这个配置类非常重要。能配置ttl、CacheKeyPrefix、ConversionService等等等等
  // 可以用链式操作进行构造~~~
  private final RedisCacheConfiguration defaultCacheConfig;
  private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;、
  // allow create unconfigured caches
  private final boolean allowInFlightCacheCreation;
  // 请注意:这个构造函数是私有的
  private RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation) {
    Assert.notNull(cacheWriter, "CacheWriter must not be null!");
    Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
    this.cacheWriter = cacheWriter;
    this.defaultCacheConfig = defaultCacheConfiguration;
    this.initialCacheConfiguration = new LinkedHashMap<>();
    this.allowInFlightCacheCreation = allowInFlightCacheCreation;
  }
  public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
    this(cacheWriter, defaultCacheConfiguration, true);
  }
  public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
    this(cacheWriter, defaultCacheConfiguration, true, initialCacheNames);
  }
  public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
    this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation);
    // 给每个Cache都放一个配置,默认都使用全局的defaultCacheConfiguration
    // 可见:它是支持给不同的Cache,给出不同的配置的(比如过期时间,,,等等等)
    for (String cacheName : initialCacheNames) {
      this.initialCacheConfiguration.put(cacheName, defaultCacheConfiguration);
    }
  }
  // 这里也可以自己把initialCacheConfigurations这个Map传进来(完成完全个性化)
  public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
    this(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, true);
  }
  ...
  // 根据RedisConnectionFactory 直接new一个RedisCacheManager出来,都采用默认的方案
  public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
    return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory), RedisCacheConfiguration.defaultCacheConfig());
  }
  //builder默认来构造、设置更加详细的参数设置
  // 注意这个builder,既能够根据RedisConnectionFactory生成,也能个根据`RedisCacheWriter`直接生成
  public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {
    Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
    return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
  }
  public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) {
    Assert.notNull(cacheWriter, "CacheWriter must not be null!");
    return RedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
  }
  // 实现抽象父类的抽象方法,把指定的缓存Cache都加载进来~ 
  // 若没有指定,这里木有啦~
  @Override
  protected Collection<RedisCache> loadCaches() {
    List<RedisCache> caches = new LinkedList<>();
    for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
      caches.add(createRedisCache(entry.getKey(), entry.getValue()));
    }
    return caches;
  }
  // 此处使用的是RedisCache,它其实里面依赖的是RedisCacheWriter(1.x版本传入的是RedisOperations,也就是RedisTemplate)
  protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
    return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
  }
  // 简单的说,默认下RedisCache也是允许动态生成的~~~~
  // 显然,默认生成的(并非自己指定的),只能使用默认配置defaultCacheConfig了
  @Override
  protected RedisCache getMissingCache(String name) {
    return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
  }
  public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
  ... // 获取到全部的Cache的配置(包括只传了CacheNames的情况)  它的key就是所有的cacheNames
  }
  // builder模式,详细代码省略~  
  public static class RedisCacheManagerBuilder {
    ...
    public RedisCacheManager build() {
      RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation);
      // 是否允许事务:默认是false
      cm.setTransactionAware(enableTransactions);
      return cm;
    }
  }
}


这个实现看着还是非常简单的,因为它有2.0提供的两个新的类:RedisCacheWriter和RedisCacheConfiguration进行分而治之。


RedisCache

很显然,RedisCache是对Cache抽象的实现:


同样的,1.x和2.x对此类的实现完全不一样,此处就不贴图了~


// @since 2.0   请注意:这里也是以2.0版本的为准的
public class RedisCache extends AbstractValueAdaptingCache {
  private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
  private final String name; // 缓存的名字
  private final RedisCacheWriter cacheWriter; //最终操作是委托给它的
  private final RedisCacheConfiguration cacheConfig; // cache的配置(因为每个Cache的配置可能不一样,即使来自于同一个CacheManager)
  private final ConversionService conversionService; // 数据转换
  // 唯一构造函数,并且还是protected 的。可见我们自己并不能操控它
  protected RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
    super(cacheConfig.getAllowCacheNullValues());
    Assert.notNull(name, "Name must not be null!");
    Assert.notNull(cacheWriter, "CacheWriter must not be null!");
    Assert.notNull(cacheConfig, "CacheConfig must not be null!");
    this.name = name;
    this.cacheWriter = cacheWriter;
    this.cacheConfig = cacheConfig;
    this.conversionService = cacheConfig.getConversionService();
  }
  @Override
  public String getName() {
    return name;
  }
  @Override
  public RedisCacheWriter getNativeCache() {
    return this.cacheWriter;
  }
  // cacheWriter会有网络访问请求~~~去访问服务器
  @Override
  protected Object lookup(Object key) {
    byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key));
    if (value == null) {
      return null;
    }
    return deserializeCacheValue(value);
  }
  @Override
  @Nullable
  public ValueWrapper get(Object key) {
    Object value = lookup(key);
    return toValueWrapper(value);
  }
  // 请注意:这个方法因为它要保证同步性,所以使用了synchronized 
  // 还记得我说过的sync=true这个属性吗,靠的就是它来保证的(当然在分布式情况下 不能百分百保证)
  @Override
  @SuppressWarnings("unchecked")
  public synchronized <T> T get(Object key, Callable<T> valueLoader) {
    ValueWrapper result = get(key);
    if (result != null) { // 缓存里有,就直接返回吧
      return (T) result.get();
    }
    // 缓存里没有,那就从valueLoader里拿值,拿到后马上put进去
    T value = valueFromLoader(key, valueLoader);
    put(key, value); 
    return value;
  }
  @Override
  public void put(Object key, @Nullable Object value) {
    Object cacheValue = preProcessCacheValue(value);
    // 对null值的判断,看看是否允许存储它呢~~~
    if (!isAllowNullValues() && cacheValue == null) {
      throw new IllegalArgumentException(String.format("Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.", name));
    }
    cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
  }
  // @since 4.1
  @Override
  public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
  ...
  }
  @Override
  public void evict(Object key) {
    cacheWriter.remove(name, createAndConvertCacheKey(key));
  }
  // 清空:在redis中慎用。因为它是传一个*去  进行全部删除的。
  @Override
  public void clear() {
    byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
    cacheWriter.clean(name, pattern);
  }
  public RedisCacheConfiguration getCacheConfiguration() {
    return cacheConfig;
  }
  // 序列化key、value   ByteUtils为redis包的工具类,Data Redis 1.7
  protected byte[] serializeCacheKey(String cacheKey) {
    return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey));
  }
  protected byte[] serializeCacheValue(Object value) {
    if (isAllowNullValues() && value instanceof NullValue) {
      return BINARY_NULL_VALUE;
    }
    return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value));
  }
  // 创建一个key,请注意这里prefixCacheKey() 前缀生效了~~~
  protected String createCacheKey(Object key) {
    String convertedKey = convertKey(key);
    if (!cacheConfig.usePrefix()) {
      return convertedKey;
    }
    return prefixCacheKey(convertedKey);
  }
  ... 
}


RedisCache它持有RedisCacheWriter的引用,所以的对Redis服务的操作都是委托给了它。同时它还持有RedisCacheConfiguration和ConversionService,证明即使是同一个CacheManager管理的缓存实例,配置都可能是不一样的,支持到了很强的个性化。

相关文章
|
8月前
|
NoSQL Java 数据库连接
《深入理解Spring》Spring Data——数据访问的统一抽象与极致简化
Spring Data通过Repository抽象和方法名派生查询,简化数据访问层开发,告别冗余CRUD代码。支持JPA、MongoDB、Redis等多种存储,统一编程模型,提升开发效率与架构灵活性,是Java开发者必备利器。(238字)
|
8月前
|
存储 Java 关系型数据库
Spring Boot中Spring Data JPA的常用注解
Spring Data JPA通过注解简化数据库操作,实现实体与表的映射。常用注解包括:`@Entity`、`@Table`定义表结构;`@Id`、`@GeneratedValue`配置主键策略;`@Column`、`@Transient`控制字段映射;`@OneToOne`、`@OneToMany`等处理关联关系;`@Enumerated`、`@NamedQuery`支持枚举与命名查询。合理使用可提升开发效率与代码可维护性。(238字)
701 1
|
9月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1614 5
存储 JSON Java
929 0
|
9月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
790 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
9月前
|
存储 缓存 Java
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
缓存是提升应用性能的重要技术,Spring框架提供了丰富的缓存注解,如`@Cacheable`、`@CacheEvict`等,帮助开发者简化缓存管理。本文介绍了如何在Spring中配置缓存管理器,使用缓存注解优化数据访问,并探讨了缓存的最佳实践,以提升系统响应速度与可扩展性。
426 0
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
|
9月前
|
SQL Java 数据库连接
Spring Data JPA 技术深度解析与应用指南
本文档全面介绍 Spring Data JPA 的核心概念、技术原理和实际应用。作为 Spring 生态系统中数据访问层的关键组件,Spring Data JPA 极大简化了 Java 持久层开发。本文将深入探讨其架构设计、核心接口、查询派生机制、事务管理以及与 Spring 框架的集成方式,并通过实际示例展示如何高效地使用这一技术。本文档约1500字,适合有一定 Spring 和 JPA 基础的开发者阅读。
837 0
|
11月前
|
存储 缓存 NoSQL
Spring Cache缓存框架
Spring Cache是Spring体系下的标准化缓存框架,支持多种缓存(如Redis、EhCache、Caffeine),可独立或组合使用。其优势包括平滑迁移、注解与编程两种使用方式,以及高度解耦和灵活管理。通过动态代理实现缓存操作,适用于不同业务场景。
748 0
|
11月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
792 2