背景:需要使用redis注解方式,缓存数据。
要求1:需要对复杂传参进行处理。(使用自定义模板)(提供两个是为了说明redis可以自定义n个模板提供调用)
要求2:需要设置过期时间。(注解不支持设置过期时间,所以需要修改解析方法)
一、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<optional>true</optional>
</dependency>
二、修改启动类和配置文件
@EnableCaching
public class WebApplication extends SpringBootServletInitializer
spring:
cache:
type: redis
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 1000
三、添加两个自定义解析模板
-
模板1
package ffcs.zzk.redis.config; import org.springframework.cache.CacheManager; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; @Component public class CustomKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { String id = params[0].toString(); List<Map<String, Object>> datas = (List<Map<String, Object>>) params[1]; StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(id+"-"); for (Map<String, Object> data : datas) { if (data.containsKey("name") && data.containsKey("age")) { keyBuilder.append(data.get("name").toString()); keyBuilder.append("_"); keyBuilder.append(data.get("age").toString()); keyBuilder.append("-"); } } return keyBuilder.toString(); } }
-
模板2
package ffcs.zzk.redis.config; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.List; import java.util.Map; @Component public class CustomKeyGenerator2 implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { String id = params[0].toString(); List<Map<String, Object>> datas = (List<Map<String, Object>>) params[1]; StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(id+"|"); for (Map<String, Object> data : datas) { if (data.containsKey("name") && data.containsKey("age")) { keyBuilder.append(data.get("name").toString()); keyBuilder.append("_"); keyBuilder.append(data.get("age").toString()); keyBuilder.append("|"); } } return keyBuilder.toString(); } }
四、配置自定义方法解析
package ffcs.zzk.redis.config;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.StringUtils;
import java.time.Duration;
public class CustomRedisCacheManager extends RedisCacheManager {
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
/**
* 重写createRedisCache方法
* @param name 原来的name只是作为redis存储键名
* 重写的name可通过"#"拼接过期时间:
* 1. 如果没有"#"则默认不设置过期时间
* 2. 拼接的第一个"#"后面为过期时间,第二个"#"后面为时间单位
* 3. 时间单位的表示使用: d(天)、h(小时)、m(分钟)、s(秒), 默认为h(小时)
* @param cacheConfig
* @return
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
// 解析name,设置过期时间
if (name!=null && name.contains("#")) {
String[] split = name.split("#");
// 缓存键名
String cacheName = split[0];
// "#"后第一位是时间
int expire = Integer.parseInt(split[1]);
// 过期时间,默认为h(小时)
Duration duration = Duration.ofHours(expire);
// 根据"#"后第二位字符判断过期时间的单位,设置相应的过期时间,默认时间单位是h(小时)
if (split.length == 3) {
switch (split[2]){
case "d":
duration = Duration.ofDays(expire);
break;
case "m":
duration = Duration.ofMinutes(expire);
break;
case "s":
duration = Duration.ofSeconds(expire);
break;
default:
duration = Duration.ofHours(expire);
}
}
return super.createRedisCache(cacheName, cacheConfig.entryTtl(duration));
}
return super.createRedisCache(name, cacheConfig);
}
}
五、调用自定义解析
package ffcs.zzk.redis.config;
import ffcs.zzk.redis.config.CustomRedisCacheManager;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
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.cache.RedisCacheWriter;
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 org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Objects;
/**
* @author 天真热
* @create 2022-09-24 13:37
* @desc
**/
@Configuration
public class RedisConfiguration {
/**
* 调用自定义的方法处理注解
* @param redisTemplate
* @return
*/
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// 设置key采用String的序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer.UTF_8))
//设置value序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new CustomRedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
/**
* @param redisConnectionFactory:配置不同的客户端,这里注入的redis连接工厂不同: JedisConnectionFactory、LettuceConnectionFactory
* @功能描述 :配置Redis序列化,原因如下:
* (1) StringRedisTemplate的序列化方式为字符串序列化,
* RedisTemplate的序列化方式默为jdk序列化(实现Serializable接口)
* (2) RedisTemplate的jdk序列化方式在Redis的客户端中为乱码,不方便查看,
* 因此一般修改RedisTemplate的序列化为方式为JSON方式【建议使用GenericJackson2JsonRedisSerializer】
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = serializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// key采用String的序列化方式
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
//hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
/**
* 此方法不能用@Ben注解,避免替换Spring容器中的同类型对象
*/
public GenericJackson2JsonRedisSerializer serializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
六、缓存示例
-
简单测试
package ffcs.zzk.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author 天真热 * @create 2024-02-19 19:42 * @desc **/ @RestController @RequestMapping("/redis") public class RedisController { @Autowired RedisService redisService; @RequestMapping("/test") public String test() { return redisService.getdata(); } @RequestMapping("/test1") public String test1() { List<Map<String,Object>> datas=new ArrayList<>(); Map map1=new HashMap(); map1.put("name","小白"); map1.put("age","10"); datas.add(map1); Map map2=new HashMap(); map2.put("name","小都"); map2.put("age","15"); datas.add(map2); Map map3=new HashMap(); map3.put("name","小的"); map3.put("age","19"); datas.add(map3); return redisService.addCacheByMethod(10,datas); } @RequestMapping("/test2") public String test2() { List<Map<String,Object>> datas=new ArrayList<>(); Map map1=new HashMap(); map1.put("name","小白"); map1.put("age","10"); datas.add(map1); Map map2=new HashMap(); map2.put("name","小都"); map2.put("age","15"); datas.add(map2); Map map3=new HashMap(); map3.put("name","小的"); map3.put("age","19"); datas.add(map3); return redisService.addCacheByMethod2(10,datas); } }
package ffcs.zzk.redis; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author 天真热 * @create 2024-02-19 19:42 * @desc **/ @Service public class RedisService { @Cacheable(value = "space", key = "'key'", sync = true) public String getdata() { SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sf.format(new Date()); } @Cacheable(value = "space#3#s", keyGenerator = "customKeyGenerator") public String addCacheByMethod(int id, List<Map<String, Object>> datas) { Map<String, Object> map = new HashMap<>(); map.put("name", "demo"); map.put("value", "this is " + id + " cache"); return "xxxx"; } @Cacheable(value = "space#3#s", keyGenerator = "customKeyGenerator2") public String addCacheByMethod2(int id, List<Map<String, Object>> datas) { Map<String, Object> map = new HashMap<>(); map.put("name", "demo"); map.put("value", "this is " + id + " cache"); return "xxxx"; } }