目录
一、SpringBoot集成SpringCache
1.集成SpringCache,自需要在pom中加入以下依赖:
2.需要在application.properties中配置属性:
3.使用@EnableCaching注解开启缓存
4.简单介绍常用注解
二、自定义KeyGenerator
1.实现KeyGenerator接口generate方法
2.使用MyKeyGenerator自定义的key生成策略
三、设置单个key的缓存时间
1.创建MyRedisCacheWriter实现RedisCacheWriter接口
2.配置实现类到CacheManager
3.使用
SpringBoot本身提供了一个基于 ConcurrentHashMap缓存机制,也集成了 EhCache2.x、JCache CJSR-107、Couchbase、Redis等。 SpringBoot应用通过注解的方式使用统一的缓存,只需在方法上使用缓存注解即可,其缓存的具体实现依赖于选择的目标缓存管理器。因为SpringCache的注解是采用Spring Aop来动态代理的,同个类中的调用无法生效。本文采用redis缓存机制。
org.springframework.boot
spring-boot-starter-cache
spring.cache.type=redis
其他值含义:
Simple:基于ConcurrentHashMap 实现的缓存,适合单体应用或者开发环境使用;
none:关闭缓存;
@SpringBootApplication
@EnableCaching
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class);
}
}
@Cacheable ,作用在方法上,触发缓存读取操作
@CacheEvict ,作用在方法上,触发缓存失效操作
@CachePut ,作用在方法上,触发缓存更新操作。
@Cache ,作用在方法上,综合上面的各种操作,在有些场景下 ,调用业务会触发多种缓存操作。
@CacheConfig ,在类上设置当前缓存 一些公共设置。
背景:在做缓存时,有的特定的情况下SpringBoot自带的SimpleKeyGenerator和key生成器不能满足需求,例如在参数为map时,要求map里的所有key-value组合作为一个key来做缓存;
这里是以map为入参时为例,具体按照自己的实际情况来定
package com.ztxy.module.log.controller.conf;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author jie
* @Description:
* @date 2020/1/6 17:04
*/
@Component
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
Object param = params[0];
// 参数为map自定义key=类名+方法名+map的key-value值
if (param instanceof Map) {
StringBuilder builder = new StringBuilder();
// 分隔符
String sp = ".";
builder.append(target.getClass().getSimpleName()).append(sp);
builder.append(method.getName()).append(sp);
Map map = (Map) param;
if (map.isEmpty()) {
return builder.toString();
}
for (String key : map.keySet()) {
builder.append(key).append("-").append(map.get(key)).append(sp);
}
return builder.toString();
}
return new SimpleKey(params);
}
}
@Cacheable(cacheNames = "ids_cache",keyGenerator = "myKeyGenerator")
public List
selectList(Map map) {
List list = sysLoginRegisterLogMapper.selectListByMap(map);
return list;
}
SpringCache对redis进行处理,用的DefaultRedisCacheWriter,但是此类是没有被修饰符public所修饰的,所以只能包类使用,不能自己继承,也不能被重写方法,所以只能自己构造CacheManager,然后配置RedisCacheWriter。
put方法就是存入redis,get方法从redis中获取。我们只需要修改put方法设置过期时间即可,其他的copy的DefaultRedisCacheWriter的实现。如果需实现其他功能,修改对应其他方法即可。
package com.ztxy.module.log.controller.conf;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author jie
* @Description:
* @date 2020/1/6 17:44
*/
public class MyRedisCacheWriter implements RedisCacheWriter {
private final RedisConnectionFactory connectionFactory;
private final Duration sleepTime;
final static String REDIS_EXPIRE_TIME_KEY = "#key_expire_time";
MyRedisCacheWriter(RedisConnectionFactory connectionFactory) {
this(connectionFactory, Duration.ZERO);
}
MyRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
Assert.notNull(sleepTime, "SleepTime must not be null!");
this.connectionFactory = connectionFactory;
this.sleepTime = sleepTime;
}
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
this.execute(name, (connection) -> {
//当设置了过期时间,则修改取出
//@Cacheable(cacheNames = "ids_cache#key_expire_time=120",keyGenerator = "myKeyGenerator")
//name 对应 cacheNames
//key 对应 MyKeyGenerator 自定义生成的key
int index = name.lastIndexOf(REDIS_EXPIRE_TIME_KEY);
if(index > 0){
String expireTime = name.substring(index + 1 + REDIS_EXPIRE_TIME_KEY.length());
connection.set(key, value, Expiration.from(Long.valueOf(expireTime), TimeUnit.SECONDS), RedisStringCommands.SetOption.upsert());
}else if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
}
public byte[] get(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
String k = new String(key);
return (byte[])this.execute(name, (connection) -> {
return connection.get(key);
});
}
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return (byte[])this.execute(name, (connection) -> {
if (this.isLockingCacheWriter()) {
this.doLock(name, connection);
}
Object var6;
try {
if (!connection.setNX(key, value)) {
byte[] var10 = connection.get(key);
return var10;
}
if (shouldExpireWithin(ttl)) {
connection.pExpire(key, ttl.toMillis());
}
var6 = null;
} finally {
if (this.isLockingCacheWriter()) {
this.doUnlock(name, connection);
}
}
return (byte[])var6;
});
}
public void remove(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
this.execute(name, (connection) -> {
return connection.del(new byte[][]{key});
});
}
public void clean(String name, byte[] pattern) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(pattern, "Pattern must not be null!");
this.execute(name, (connection) -> {
boolean wasLocked = false;
try {
if (this.isLockingCacheWriter()) {
this.doLock(name, connection);
wasLocked = true;
}
byte[][] keys = (byte[][])((Set) Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())).toArray(new byte[0][]);
if (keys.length > 0) {
connection.del(keys);
}
} finally {
if (wasLocked && this.isLockingCacheWriter()) {
this.doUnlock(name, connection);
}
}
return "OK";
});
}
void lock(String name) {
this.execute(name, (connection) -> {
return this.doLock(name, connection);
});
}
void unlock(String name) {
this.executeLockFree((connection) -> {
this.doUnlock(name, connection);
});
}
private Boolean doLock(String name, RedisConnection connection) {
return connection.setNX(createCacheLockKey(name), new byte[0]);
}
private Long doUnlock(String name, RedisConnection connection) {
return connection.del(new byte[][]{createCacheLockKey(name)});
}
boolean doCheckLock(String name, RedisConnection connection) {
return connection.exists(createCacheLockKey(name));
}
private boolean isLockingCacheWriter() {
return !this.sleepTime.isZero() && !this.sleepTime.isNegative();
}
private T execute(String name, Function callback) {
RedisConnection connection = this.connectionFactory.getConnection();
T var4;
try {
this.checkAndPotentiallyWaitUntilUnlocked(name, connection);
var4 = callback.apply(connection);
} finally {
connection.close();
}
return var4;
}
private void executeLockFree(Consumer callback) {
RedisConnection connection = this.connectionFactory.getConnection();
try {
callback.accept(connection);
} finally {
connection.close();
}
}
private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
if (this.isLockingCacheWriter()) {
try {
while(this.doCheckLock(name, connection)) {
Thread.sleep(this.sleepTime.toMillis());
}
} catch (InterruptedException var4) {
Thread.currentThread().interrupt();
throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name), var4);
}
}
}
private static boolean shouldExpireWithin(@Nullable Duration ttl) {
return ttl != null && !ttl.isZero() && !ttl.isNegative();
}
private static byte[] createCacheLockKey(String name) {
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
}
}
配置springCache的CacheManager需继承CachingConfigurerSupport类,重写cacheManager方法,在此之前,确保spring容器里有redisTemplate,需使用redisTemplate获取RedisConnectionFactory
package com.ztxy.module.log.controller.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置类
* @author xxx
* @date 2019年8月6日
*
*/
@Configuration
public class RedisAutoConfiguration {
@Bean
@SuppressWarnings("all")
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
继承CachingConfigurerSupport类,重写cacheManager方法
package com.ztxy.module.log.controller.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
* @author jie
* @Description:
* @date 2020/1/6 17:56
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheableConfiguration extends CachingConfigurerSupport {
@Autowired
private RedisTemplate redisTemplate;
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).entryTtl(Duration.ofSeconds(30));
return configuration;
}
@Override
public CacheManager cacheManager() {
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
MyRedisCacheWriter cacheWriter = new MyRedisCacheWriter(connectionFactory);
CacheManager redisCacheManager = new RedisCacheManager(cacheWriter, redisCacheConfiguration());
return redisCacheManager;
}
}
实现原理就是,从cacheNames = "ids_cache#key_expire_time=120"中取出我自定义的过期时间,keyGenerator = "myKeyGenerator"是上文中自定义的key生成器。
@Cacheable(cacheNames = "ids_cache#key_expire_time=120",keyGenerator = "myKeyGenerator")
public List selectList(Map map) {
List list = sysLoginRegisterLogMapper.selectListByMap(map);
return list;
}
如有不足,欢迎批评指正
参考文章:https://blog.csdn.net/u013685413/article/details/88556880