006-spring cache-缓存原理-SimpleCacheConfiguration、RedisCacheConfiguration原理实现
一、实现说明
事务回滚:除了GuavaCacheManager之外,其他Cache都支持Spring事务,即如果事务回滚了,缓存的数据也会移除掉。
Spring不进行cache的定义,也不进行Cache的缓存策略的维护。这些都是由底层cache自己实现,然后外部创建一个cache注入进来的。Spring只是提供了一个Wrapper,提供一套对外一致的API。
CacheManger | 描述 |
---|---|
SimpleCacheManager | 使用简单的Collection来存储缓存,主要用于测试 |
ConcurrentMapCacheManager | 使用ConcurrentMap作为缓存技术(默认) |
NoOpCacheManager | 测试用 |
EhCacheCacheManager | 使用EhCache作为缓存技术,以前在hibernate的时候经常用 |
GuavaCacheManager | 使用google guava的GuavaCache作为缓存技术 |
HazelcastCacheManager | 使用Hazelcast作为缓存技术 |
JCacheCacheManager | 使用JCache标准的实现作为缓存技术,如Apache Commons JCS |
RedisCacheManager | 使用Redis作为缓存技术 |
springboot cache
static { Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); }
Spring boot默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager来实现缓存。
二、CacheManager说明
2.1、SimpleCacheConfiguration 说明
核心UML图
查看ConcurrentMapCache实现 核心是ValueWrapper
2.1.1、SimpleCacheConfiguration说明实现
代码地址:https://github.com/bjlhx15/common.git spring-cache/springboot2-cache-default
2.1.2、基础配置
2.2、RedisCacheConfiguration说明
1、RedisCacheConfiguration
配置类配置缓存管理器
/* * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure.cache; import java.util.LinkedHashSet; import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ResourceLoader; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; /** * Redis cache configuration. * * @author Stephane Nicoll * @author Mark Paluch * @author Ryon Day * @since 1.3.0 */ @Configuration @ConditionalOnClass(RedisConnectionFactory.class) @AutoConfigureAfter(RedisAutoConfiguration.class) @ConditionalOnBean(RedisConnectionFactory.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class RedisCacheConfiguration { private final CacheProperties cacheProperties; private final CacheManagerCustomizers customizerInvoker; private final org.springframework.data.redis.cache.RedisCacheConfiguration redisCacheConfiguration; RedisCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration) { this.cacheProperties = cacheProperties; this.customizerInvoker = customizerInvoker; this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable(); } @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) { RedisCacheManagerBuilder builder = RedisCacheManager .builder(redisConnectionFactory) .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader())); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames)); } return this.customizerInvoker.customize(builder.build()); } private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration( ClassLoader classLoader) { if (this.redisCacheConfiguration != null) { return this.redisCacheConfiguration; } Redis redisProperties = this.cacheProperties.getRedis(); org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration .defaultCacheConfig(); config = config.serializeValuesWith(SerializationPair .fromSerializer(new JdkSerializationRedisSerializer(classLoader))); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } }
注入方法
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) { RedisCacheManagerBuilder builder = RedisCacheManager .builder(redisConnectionFactory) .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader())); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames)); } return this.customizerInvoker.customize(builder.build()); }
可以看到 核心是 创建了一个 RedisCacheManager,同时使用:initialCacheNames(new LinkedHashSet<>(cacheNames));
2、RedisCacheManager
继承关系:RedisCacheManager→AbstractTransactionSupportingCacheManager【事务支持抽象类】→ AbstractCacheManager→CacheManager、InitializingBean
CacheManager:提供原始管理接口方法
InitializingBean:主要作用是Bean初始化后执行一个afterPropertiesSet方法 003-Spring4 扩展分析-spring类初始化@PostConstruct > InitializingBean > init-method、ApplicationContext、BeanPostProcessor、BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
AbstractCacheManager:afterPropertiesSet中调用 initializeCaches
public void initializeCaches() { Collection<? extends Cache> caches = loadCaches(); synchronized (this.cacheMap) { this.cacheNames = Collections.emptySet(); this.cacheMap.clear(); Set<String> cacheNames = new LinkedHashSet<>(caches.size()); for (Cache cache : caches) { String name = cache.getName(); this.cacheMap.put(name, decorateCache(cache)); cacheNames.add(name); } this.cacheNames = Collections.unmodifiableSet(cacheNames); } }
其一调用了:loadCaches;其二 初始化缓存名
RedisCacheManager主要管理缓存
/* * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.redis.cache; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * {@link org.springframework.cache.CacheManager} backed by a {@link RedisCache Redis} cache. * <p /> * This cache manager creates caches by default upon first write. Empty caches are not visible on Redis due to how Redis * represents empty data structures. * <p /> * Caches requiring a different {@link RedisCacheConfiguration} than the default configuration can be specified via * {@link RedisCacheManagerBuilder#withInitialCacheConfigurations(Map)}. * * @author Christoph Strobl * @author Mark Paluch * @since 2.0 * @see RedisCacheConfiguration * @see RedisCacheWriter */ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { private final RedisCacheWriter cacheWriter; private final RedisCacheConfiguration defaultCacheConfig; private final Map<String, RedisCacheConfiguration> initialCacheConfiguration; private final boolean allowInFlightCacheCreation; /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. * @param allowInFlightCacheCreation allow create unconfigured caches. * @since 2.0.4 */ 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; } /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. */ public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { this(cacheWriter, defaultCacheConfiguration, true); } /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. * @param initialCacheNames optional set of known cache names that will be created with given * {@literal defaultCacheConfiguration}. */ public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) { this(cacheWriter, defaultCacheConfiguration, true, initialCacheNames); } /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. * @param allowInFlightCacheCreation if set to {@literal true} no new caches can be acquire at runtime but limited to * the given list of initial cache names. * @param initialCacheNames optional set of known cache names that will be created with given * {@literal defaultCacheConfiguration}. * @since 2.0.4 */ public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) { this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation); for (String cacheName : initialCacheNames) { this.initialCacheConfiguration.put(cacheName, defaultCacheConfiguration); } } /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. * @param initialCacheConfigurations Map of known cache names along with the configuration to use for those caches. * Must not be {@literal null}. */ public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) { this(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, true); } /** * Creates new {@link RedisCacheManager} using given {@link RedisCacheWriter} and default * {@link RedisCacheConfiguration}. * * @param cacheWriter must not be {@literal null}. * @param defaultCacheConfiguration must not be {@literal null}. Maybe just use * {@link RedisCacheConfiguration#defaultCacheConfig()}. * @param initialCacheConfigurations Map of known cache names along with the configuration to use for those caches. * Must not be {@literal null}. * @param allowInFlightCacheCreation if set to {@literal false} this cache manager is limited to the initial cache * configurations and will not create new caches at runtime. * @since 2.0.4 */ public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) { this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation); Assert.notNull(initialCacheConfigurations, "InitialCacheConfigurations must not be null!"); this.initialCacheConfiguration.putAll(initialCacheConfigurations); } /** * Create a new {@link RedisCacheManager} with defaults applied. * <dl> * <dt>locking</dt> * <dd>disabled</dd> * <dt>cache configuration</dt> * <dd>{@link RedisCacheConfiguration#defaultCacheConfig()}</dd> * <dt>initial caches</dt> * <dd>none</dd> * <dt>transaction aware</dt> * <dd>no</dd> * <dt>in-flight cache creation</dt> * <dd>enabled</dd> * </dl> * * @param connectionFactory must not be {@literal null}. * @return new instance of {@link RedisCacheManager}. */ public static RedisCacheManager create(RedisConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null!"); return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory), RedisCacheConfiguration.defaultCacheConfig()); } /** * Entry point for builder style {@link RedisCacheManager} configuration. * * @param connectionFactory must not be {@literal null}. * @return new {@link RedisCacheManagerBuilder}. */ public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null!"); return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory); } /** * Entry point for builder style {@link RedisCacheManager} configuration. * * @param cacheWriter must not be {@literal null}. * @return new {@link RedisCacheManagerBuilder}. */ public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) { Assert.notNull(cacheWriter, "CacheWriter must not be null!"); return RedisCacheManagerBuilder.fromCacheWriter(cacheWriter); } /* * (non-Javadoc) * @see org.springframework.cache.support.AbstractCacheManager#loadCaches() */ @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; } /* * (non-Javadoc) * @see org.springframework.cache.support.AbstractCacheManager#getMissingCache(java.lang.String) */ @Override protected RedisCache getMissingCache(String name) { return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null; } /** * @return unmodifiable {@link Map} containing cache name / configuration pairs. Never {@literal null}. */ public Map<String, RedisCacheConfiguration> getCacheConfigurations() { Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size()); getCacheNames().forEach(it -> { RedisCache cache = RedisCache.class.cast(lookupCache(it)); configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null); }); return Collections.unmodifiableMap(configurationMap); } /** * Configuration hook for creating {@link RedisCache} with given name and {@code cacheConfig}. * * @param name must not be {@literal null}. * @param cacheConfig can be {@literal null}. * @return never {@literal null}. */ protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) { return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig); } /** * Configurator for creating {@link RedisCacheManager}. * * @author Christoph Strobl * @author Mark Strobl * @author Kezhu Wang * @since 2.0 */ public static class RedisCacheManagerBuilder { private final RedisCacheWriter cacheWriter; private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>(); private boolean enableTransactions; boolean allowInFlightCacheCreation = true; private RedisCacheManagerBuilder(RedisCacheWriter cacheWriter) { this.cacheWriter = cacheWriter; } /** * Entry point for builder style {@link RedisCacheManager} configuration. * * @param connectionFactory must not be {@literal null}. * @return new {@link RedisCacheManagerBuilder}. */ public static RedisCacheManagerBuilder fromConnectionFactory(RedisConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null!"); return builder(new DefaultRedisCacheWriter(connectionFactory)); } /** * Entry point for builder style {@link RedisCacheManager} configuration. * * @param cacheWriter must not be {@literal null}. * @return new {@link RedisCacheManagerBuilder}. */ public static RedisCacheManagerBuilder fromCacheWriter(RedisCacheWriter cacheWriter) { Assert.notNull(cacheWriter, "CacheWriter must not be null!"); return new RedisCacheManagerBuilder(cacheWriter); } /** * Define a default {@link RedisCacheConfiguration} applied to dynamically created {@link RedisCache}s. * * @param defaultCacheConfiguration must not be {@literal null}. * @return this {@link RedisCacheManagerBuilder}. */ public RedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) { Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!"); this.defaultCacheConfiguration = defaultCacheConfiguration; return this; } /** * Enable {@link RedisCache}s to synchronize cache put/evict operations with ongoing Spring-managed transactions. * * @return this {@link RedisCacheManagerBuilder}. */ public RedisCacheManagerBuilder transactionAware() { this.enableTransactions = true; return this; } /** * Append a {@link Set} of cache names to be pre initialized with current {@link RedisCacheConfiguration}. * <strong>NOTE:</strong> This calls depends on {@link #cacheDefaults(RedisCacheConfiguration)} using whatever * default {@link RedisCacheConfiguration} is present at the time of invoking this method. * * @param cacheNames must not be {@literal null}. * @return this {@link RedisCacheManagerBuilder}. */ public RedisCacheManagerBuilder initialCacheNames(Set<String> cacheNames) { Assert.notNull(cacheNames, "CacheNames must not be null!"); Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size()); cacheNames.forEach(it -> cacheConfigMap.put(it, defaultCacheConfiguration)); return withInitialCacheConfigurations(cacheConfigMap); } /** * Append a {@link Map} of cache name/{@link RedisCacheConfiguration} pairs to be pre initialized. * * @param cacheConfigurations must not be {@literal null}. * @return this {@link RedisCacheManagerBuilder}. */ public RedisCacheManagerBuilder withInitialCacheConfigurations( Map<String, RedisCacheConfiguration> cacheConfigurations) { Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!"); cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration, String.format("RedisCacheConfiguration for cache %s must not be null!", cacheName))); this.initialCaches.putAll(cacheConfigurations); return this; } /** * Disable in-flight {@link org.springframework.cache.Cache} creation for unconfigured caches. * <p /> * {@link RedisCacheManager#getMissingCache(String)} returns {@literal null} for any unconfigured * {@link org.springframework.cache.Cache} instead of a new {@link RedisCache} instance. This allows eg. * {@link org.springframework.cache.support.CompositeCacheManager} to chime in. * * @return this {@link RedisCacheManagerBuilder}. * @since 2.0.4 */ public RedisCacheManagerBuilder disableCreateOnMissingCache() { this.allowInFlightCacheCreation = false; return this; } /** * Create new instance of {@link RedisCacheManager} with configuration options applied. * * @return new instance of {@link RedisCacheManager}. */ public RedisCacheManager build() { RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation); cm.setTransactionAware(enableTransactions); return cm; } } }
这里使用了建造者模式:002-创建型-04-建造者模式(Builder)、JDK1.7源码中的建造者模式、Spring中的建造者模式
这里重写了:Collection<RedisCache> loadCaches()
@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; }
所以在Bean初始化后会加载 RedisCache
3、RedisCache
直接看代码
/* * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.redis.cache; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.concurrent.Callable; import org.springframework.cache.support.AbstractValueAdaptingCache; import org.springframework.cache.support.NullValue; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.util.ByteUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.cache.Cache} implementation using for Redis as underlying store. * <p /> * Use {@link RedisCacheManager} to create {@link RedisCache} instances. * * @author Christoph Strobl * @author Mark Paluch * @since 2.0 * @see RedisCacheConfiguration * @see RedisCacheWriter */ public class RedisCache extends AbstractValueAdaptingCache { private static final byte[] BINARY_NULL_VALUE = new JdkSerializationRedisSerializer().serialize(NullValue.INSTANCE); private final String name; private final RedisCacheWriter cacheWriter; private final RedisCacheConfiguration cacheConfig; private final ConversionService conversionService; /** * Create new {@link RedisCache}. * * @param name must not be {@literal null}. * @param cacheWriter must not be {@literal null}. * @param cacheConfig must not be {@literal null}. */ 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(); } /* * (non-Javadoc) * @see org.springframework.cache.support.AbstractValueAdaptingCache#lookup(java.lang.Object) */ @Override protected Object lookup(Object key) { byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); if (value == null) { return null; } return deserializeCacheValue(value); } /* * (non-Javadoc) * @see org.springframework.cache.Cache#getName() */ @Override public String getName() { return name; } /* * (non-Javadoc) * @see org.springframework.cache.Cache#getNativeCache() */ @Override public RedisCacheWriter getNativeCache() { return this.cacheWriter; } /* * (non-Javadoc) * @see org.springframework.cache.Cache#get(java.lang.Object, java.util.concurrent.Callable) */ @Override @SuppressWarnings("unchecked") public synchronized <T> T get(Object key, Callable<T> valueLoader) { ValueWrapper result = get(key); if (result != null) { return (T) result.get(); } T value = valueFromLoader(key, valueLoader); put(key, value); return value; } /* * (non-Javadoc) * @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object) */ @Override public void put(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); 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()); } /* * (non-Javadoc) * @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object) */ @Override public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); if (!isAllowNullValues() && cacheValue == null) { return get(key); } byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl()); if (result == null) { return null; } return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))); } /* * (non-Javadoc) * @see org.springframework.cache.Cache#evict(java.lang.Object) */ @Override public void evict(Object key) { cacheWriter.remove(name, createAndConvertCacheKey(key)); } /* * (non-Javadoc) * @see org.springframework.cache.Cache#clear() */ @Override public void clear() { byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class); cacheWriter.clean(name, pattern); } /** * Get {@link RedisCacheConfiguration} used. * * @return immutable {@link RedisCacheConfiguration}. Never {@literal null}. */ public RedisCacheConfiguration getCacheConfiguration() { return cacheConfig; } /** * Customization hook called before passing object to * {@link org.springframework.data.redis.serializer.RedisSerializer}. * * @param value can be {@literal null}. * @return preprocessed value. Can be {@literal null}. */ @Nullable protected Object preProcessCacheValue(@Nullable Object value) { if (value != null) { return value; } return isAllowNullValues() ? NullValue.INSTANCE : null; } /** * Serialize the key. * * @param cacheKey must not be {@literal null}. * @return never {@literal null}. */ protected byte[] serializeCacheKey(String cacheKey) { return ByteUtils.getBytes(cacheConfig.getKeySerializationPair().write(cacheKey)); } /** * Serialize the value to cache. * * @param value must not be {@literal null}. * @return never {@literal null}. */ protected byte[] serializeCacheValue(Object value) { if (isAllowNullValues() && value instanceof NullValue) { return BINARY_NULL_VALUE; } return ByteUtils.getBytes(cacheConfig.getValueSerializationPair().write(value)); } /** * Deserialize the given value to the actual cache value. * * @param value must not be {@literal null}. * @return can be {@literal null}. */ @Nullable protected Object deserializeCacheValue(byte[] value) { if (isAllowNullValues() && ObjectUtils.nullSafeEquals(value, BINARY_NULL_VALUE)) { return NullValue.INSTANCE; } return cacheConfig.getValueSerializationPair().read(ByteBuffer.wrap(value)); } /** * Customization hook for creating cache key before it gets serialized. * * @param key will never be {@literal null}. * @return never {@literal null}. */ protected String createCacheKey(Object key) { String convertedKey = convertKey(key); if (!cacheConfig.usePrefix()) { return convertedKey; } return prefixCacheKey(convertedKey); } /** * Convert {@code key} to a {@link String} representation used for cache key creation. * * @param key will never be {@literal null}. * @return never {@literal null}. * @throws IllegalStateException if {@code key} cannot be converted to {@link String}. */ protected String convertKey(Object key) { TypeDescriptor source = TypeDescriptor.forObject(key); if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) { return conversionService.convert(key, String.class); } Method toString = ReflectionUtils.findMethod(key.getClass(), "toString"); if (toString != null && !Object.class.equals(toString.getDeclaringClass())) { return key.toString(); } throw new IllegalStateException( String.format("Cannot convert %s to String. Register a Converter or override toString().", source)); } private byte[] createAndConvertCacheKey(Object key) { return serializeCacheKey(createCacheKey(key)); } private String prefixCacheKey(String key) { // allow contextual cache names by computing the key prefix on every call. return cacheConfig.getKeyPrefixFor(name) + key; } private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) { try { return valueLoader.call(); } catch (Exception e) { throw new ValueRetrievalException(key, valueLoader, e); } } }
说明
private static final byte[] BINARY_NULL_VALUE = new JdkSerializationRedisSerializer().serialize(NullValue.INSTANCE); private final String name; //缓存空间名 private final RedisCacheWriter cacheWriter; //redis操作对象 private final RedisCacheConfiguration cacheConfig; //缓存的配置 与springboot里面的不一样 private final ConversionService conversionService;
1、lookup(Object key) 取值
为了父类的 :ValueWrapper get(Object key); 以及<T> T get(Object key, @Nullable Class<T> type);使用 这两个是Cache 接口要求的
@Override protected Object lookup(Object key) { byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); if (value == null) { return null; } return deserializeCacheValue(value); }
在基础存储中执行实际查找。
说明:lookup 简单理解,向上看给上面用的,然后翻看RedisCache的 AbstractValueAdaptingCache 父类。
@Override public ValueWrapper get(Object key) { Object value = lookup(key); return toValueWrapper(value); } @Overridepublic <T> T get(Object key, @Nullable Class<T> type) { Object value = fromStoreValue(lookup(key)); if (value != null && type != null && !type.isInstance(value)) { throw new IllegalStateException( "Cached value is not of required type [" + type.getName() + "]: " + value); } return (T) value; } @Nullable protected abstract Object lookup(Object key);
原来如此,标准模板方法 004-行为型-02-模板方法模式(Template Method)
其一、ValueWrapper get(Object key) 转换成ValueWrapper
toValueWrapper
protected Cache.ValueWrapper toValueWrapper(@Nullable Object storeValue) { return (storeValue != null ? new SimpleValueWrapper(fromStoreValue(storeValue)) : null); }
fromStoreValue 判空,返回原值
protected Object fromStoreValue(@Nullable Object storeValue) { if (this.allowNullValues && storeValue == NullValue.INSTANCE) { return null; } return storeValue; }
SimpleValueWrapper,其实也是没做任何事,只是ValueWrapper一下
public class SimpleValueWrapper implements ValueWrapper { @Nullable private final Object value; public SimpleValueWrapper(@Nullable Object value) { this.value = value; } @Override public Object get() { return this.value; } }
其二、T get(Object key, @Nullable Class<T> type) 转换成 具体类对象
取值,判空,强制转换
2、String getName() 缓存空间名
3、RedisCacheWriter getNativeCache() 返回底层本机缓存提供程序。
4、synchronized <T> T get(Object key, Callable<T> valueLoader) 返回缓存值
@Override @SuppressWarnings("unchecked") public synchronized <T> T get(Object key, Callable<T> valueLoader) { ValueWrapper result = get(key); if (result != null) { return (T) result.get(); } T value = valueFromLoader(key, valueLoader); put(key, value); return value; }
首先调用父类的get,获取wrapper,有值强转回具体对象,否则会 valueFromLoader
valueFromLoader,从传入的方法loader加载value,
private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) { return valueLoader.call(); }
接下来put存入,返回具体对象
5、put(Object key, @Nullable Object value) 存值
//按key 缓存 值 @Override public void put(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); //空值处理 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()); }
6、ValueWrapper putIfAbsent(Object key, @Nullable Object value) 如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null.
/* * <pre><code> * Object existingValue = cache.get(key); * if (existingValue == null) { * cache.put(key, value); * return null; * } else { * return existingValue; * } * </code></pre> */ @Override public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); //空值处理 if (!isAllowNullValues() && cacheValue == null) { return get(key);//通过key去value值 } byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());//利用 缓存提供者 提供的处理 if (result == null) { return null; } return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result))); }
7、evict(Object key) 清除 指定key缓存
//清除 指定key缓存 @Override public void evict(Object key) { cacheWriter.remove(name, createAndConvertCacheKey(key)); }
createAndConvertCacheKey,以及一些列的key处理
//序列化key 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)); } //反序列化 值 @Nullable protected Object deserializeCacheValue(byte[] value) { if (isAllowNullValues() && ObjectUtils.nullSafeEquals(value, BINARY_NULL_VALUE)) { return NullValue.INSTANCE; } return cacheConfig.getValueSerializationPair().read(ByteBuffer.wrap(value)); } //key处理 protected String createCacheKey(Object key) { String convertedKey = convertKey(key); if (!cacheConfig.usePrefix()) { return convertedKey; } return prefixCacheKey(convertedKey); } //key处理 protected String convertKey(Object key) { TypeDescriptor source = TypeDescriptor.forObject(key); if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) { return conversionService.convert(key, String.class); } Method toString = ReflectionUtils.findMethod(key.getClass(), "toString"); if (toString != null && !Object.class.equals(toString.getDeclaringClass())) { return key.toString(); } throw new IllegalStateException( String.format("Cannot convert %s to String. Register a Converter or override toString().", source)); } //key处理 private byte[] createAndConvertCacheKey(Object key) { return serializeCacheKey(createCacheKey(key)); } //key处理 private String prefixCacheKey(String key) { // allow contextual cache names by computing the key prefix on every call. return cacheConfig.getKeyPrefixFor(name) + key; }
8、clear() 清除name下所有的key
//清除name下所有的key @Override public void clear() { byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class); cacheWriter.clean(name, pattern); }
第一步获取key pattern;第二步清除 查看 RedisCacheWriter的子类DefaultRedisCacheWriter的
void clean(String name, byte[] pattern)
取出匹配的key,删除掉
@Override public void clean(String name, byte[] pattern) { Assert.notNull(name, "Name must not be null!"); Assert.notNull(pattern, "Pattern must not be null!"); execute(name, connection -> { boolean wasLocked = false; try { if (isLockingCacheWriter()) { doLock(name, connection); wasLocked = true; } byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet()) .toArray(new byte[0][]); if (keys.length > 0) { connection.del(keys); } } finally { if (wasLocked && isLockingCacheWriter()) { doUnlock(name, connection); } } return "OK"; }); }