1、依赖包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.2.2.RELEASE</version> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.2.1.RELEASE</version> </dependency>
2、redis配置
方式一:
连接工厂
//@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
// RedisStandaloneConfiguration这个配置类是Spring Data Redis2.0后才有的~~~
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName("10.102.132.150");
//configuration.setPassword(RedisPassword.of("123456"));
configuration.setPort(6379);
configuration.setDatabase(0);
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
// Spring Data Redis1.x这么来设置 2.0后建议使用RedisStandaloneConfiguration来取代
//factory.setHostName("10.102.132.150");
//factory.setPassword("123456");
//factory.setPort(6379);
//factory.setDatabase(0);
return factory;
}
@Bean
public RedisTemplate<String, String> stringRedisTemplate() {
RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
方式二:采用自动配置,yml文件配置连接信息
spring:
redis:
# cluster:
# Comma-separated list of "host:port" pairs to bootstrap from.
# nodes:
# max-redirects:
database: 0
host: 127.0.0.1
#Connection URL. Overrides host, port, and password. User is ignored. Example: redis://user:password@example.com:6379
# url:
port: 6379
password:
timeout:
ssl: false
lettuce:
pool:
max-active: 1000
max-idle: 8
max-wait: -1ms
min-idle: 0
3、cacheManager、ttl、异常配置
@Slf4j
@Configuration
public class UspCacheAutoConfiguration extends CachingConfigurerSupport {
@Autowired
private ApplicationContext applicationContext;
/**
* 键默有效时间
*/
private static final Duration TTL = Duration.ofMinutes(30);
@Bean
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("redis异常: key=[{}]", key, exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("redis异常: key=[{}]", key, exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("redis异常: key=[{}]", key, exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("redis异常:", exception);
}
};
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory)
.initialCacheNames(cacheNames())
.withInitialCacheConfigurations(redisCacheConfigurationMap())
.build();
}
private Set<String> cacheNames() {
Set<String> cacheSet = new HashSet<>();
// 此处UspCacheDefinition为cache配置接口,包含擦车Name,TTL,prefix,序列化反序列化等
applicationContext.getBeansOfType(UspCacheDefinition.class).forEach((key, uspCacheDefinition) -> cacheSet.add(uspCacheDefinition.getKey()));
return cacheSet;
}
// TODO 待优化
private Map<String, RedisCacheConfiguration> redisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
applicationContext.getBeansOfType(UspCacheDefinition.class).forEach((key, uspCacheDefinition) -> {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(uspCacheDefinition.getTimeToLive())
.disableCachingNullValues();
if (uspCacheDefinition.getTimeToLive() != null) {
redisCacheConfiguration = redisCacheConfiguration.entryTtl(uspCacheDefinition.getTimeToLive());
}
if (!"".equals(uspCacheDefinition.getPrefix()) && uspCacheDefinition.getPrefix() != null) {
redisCacheConfiguration.usePrefix();
redisCacheConfiguration = redisCacheConfiguration.prefixKeysWith(uspCacheDefinition.getPrefix());
}
if (uspCacheDefinition.getSerializeKey() != null) {
redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(uspCacheDefinition.getSerializeKey());
}
if (uspCacheDefinition.getSerializeValue() != null) {
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(uspCacheDefinition.getSerializeValue());
}
redisCacheConfigurationMap.put(uspCacheDefinition.getKey(), redisCacheConfiguration);
});
return redisCacheConfigurationMap;
}
}
4、配置类上开启缓存注解支持:@EnableCaching
5、RedisCacheManager中注意RedisCacheWriter
和RedisCacheConfiguration
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
private final RedisCacheWriter cacheWriter;
// 这个配置类非常重要。能配置ttl、CacheKeyPrefix、ConversionService等等
private final RedisCacheConfiguration defaultCacheConfig;
private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;
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);
}
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);
}
public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory),
RedisCacheConfiguration.defaultCacheConfig());
}
...
/**
* Configurator for creating {@link RedisCacheManager}.
*/
public static class RedisCacheManagerBuilder {
...
public RedisCacheManager build() {
RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
allowInFlightCacheCreation);
cm.setTransactionAware(enableTransactions);
return cm;
}
}
}
6、RedisCache是对Cache的实现
// @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));
}
protected String createCacheKey(Object key) {
String convertedKey = convertKey(key);
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
return prefixCacheKey(convertedKey);
}
...
}
RedisCache持有RedisCacheWriter的引用,所有的对Redis服务的操作都是委托给了它,同时它还持有RedisCacheConfiguration和ConversionService