006-spring cache-缓存实现-01-原生实现
一、实现说明
事务回滚:除了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";
});
}
006-spring cache-缓存实现-01-原生实现的更多相关文章
- 注释驱动的 Spring cache 缓存介绍
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- [转]注释驱动的 Spring cache 缓存介绍
原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 概述 Spring 3.1 引入了激动人心的基于注释(an ...
- 注释驱动的 Spring cache 缓存介绍--转载
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- Spring cache 缓存
概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...
- 【快学SpringBoot】快速上手好用方便的Spring Cache缓存框架
前言 缓存,在开发中是非常常用的.在高并发系统中,如果没有缓存,纯靠数据库来扛,那么数据库压力会非常大,搞不好还会出现宕机的情况.本篇文章,将会带大家学习Spring Cache缓存框架. 原创声明 ...
- Spring Cache缓存注解
目录 Spring Cache缓存注解 @Cacheable 键生成器 @CachePut @CacheEvict @Caching @CacheConfig Spring Cache缓存注解 本篇文 ...
- Spring Cache缓存技术,Cacheable、CachePut、CacheEvict、Caching、CacheConfig注解的使用
前置知识: 在Spring Cache缓存中有两大组件CacheManager和Cache.在整个缓存中可以有多个CacheManager,他们负责管理他们里边的Cache.一个CacheManage ...
- Spring Cache缓存框架
一.序言 Spring Cache是Spring体系下标准化缓存框架.Spring Cache有如下优势: 缓存品种多 支持缓存品种多,常见缓存Redis.EhCache.Caffeine均支持.它们 ...
- 基于Redis的Spring cache 缓存介绍
目录 Cache API及默认提供的实现 demo 依赖包安装 定义实体类.服务类和相关配置文件 Cache注解 启用Cache注解 @CachePut @CacheEvict @Cacheable ...
- 使用Spring Cache缓存出现的小失误
前文:今天在使用Spring Boot项目使用Cache中出现的小失误,那先将自己创建项目的过程摆出来 1.首先创建一个Spring Boot的项目(我这里使用的开发工具是Intellij IDEA) ...
随机推荐
- MathType在字母上加虚线的方法
在数学中根据不同的需要,会对其中的公式或变量字母作不同的标记.这些标记在用MathType编辑数学公式时同样也要准确地编辑出来,例如在字母上方加虚线.这种数学样式在使用时也是根据自己的数学问题来使用的 ...
- linux mint 19解决 输入法问题
安装搜狗后出现 You're currently running Fcitx with GUI, but fcitx-configtool couldn't be found, the package ...
- VC++ 实现窗口抖动
RECT rect; int x, y, nWidth, nHeight; GetWindowRect(&rect); x = rect.left; y = rect.top; nWidth ...
- 异常处理----使用 try…catch…finally 处理异常
使用 try…catch…finally 处理异常 异常处理是通过try-catch-finally语句实现的. try { ...... //可能产生异常的代码 } catch( Exception ...
- POJ 1141 Brackets Sequence(区间DP, DP打印路径)
Description We give the following inductive definition of a “regular brackets” sequence: the empty s ...
- swift - UISlider 的用法
swift的UISlider的用法和oc基本没有区别 1.创建 class SecondViewController: UIViewController { var slider = UISlider ...
- Android 读写位于SD卡上的sqlite数据库文件错误问题
09-12 15:24:33.903: W/System.err(19499): java.lang.NullPointerException: Attempt to invoke virtual m ...
- Unity鼠标点击Collider
void OnGUI() { if (Event.current != null && Event.current.type == EventType.mouseDown) { )) ...
- Android性能优化的一些方案
优化Dalvik虚拟机的堆内存分配 1)首先内存方面,可以参考 Android堆内存也可自己定义大小和优化Dalvik虚拟机的堆内存分配 对于Android平台来说,其托管层使用的Dalvik Jav ...
- JSTL中<c:set>标签的用法
<c:set>标签有两种不同的属性设置:var和target. var“版本”用于设置作用域属性,target“版本”用于设置bean属性或Map值. 这两个版本都有两种形式:有标签体和没 ...