Redis和Spring Cache整合
Redis和Spring Cache整合,让能通过缓存注解优雅的操作Redis是本文的主菜。
因为Redis分布式缓存它是client/server模式,所以它的整合和前面整合Ehcache等还是有些不一样的地方的 。但在有了上篇文章做铺垫,加上上面介绍Spring Data Redis的使用之后,要集成它也是易如反掌之事。
RedisCacheManager
老规矩,整合前先看看Redis对CacheManager接口的实现RedisCacheManager:
说明:Spring Data Redis2.x对此类进行了大幅的重写,除了类名没变,内容完全重写了。
此处我给出这张对比图,希望小伙伴们也能做到心中有数。(本文以2.x版本为例)
// @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管理的缓存实例,配置都可能是不一样的,支持到了很强的个性化。