1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
| package com.tohours.bdminiapp.config.redis;
import java.io.File; import java.lang.reflect.Method; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects;
import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; 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.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import cn.hutool.core.collection.CollUtil; import lombok.extern.slf4j.Slf4j;
@EnableCaching @Configuration @Slf4j public class RedisConfig {
// yml配置的缓存时间,单位是毫秒 @Value("${spring.cache.redis.time-to-live}") private Integer timeToLive;
// 包的根路径,需要在此包下获取@Cacheable的注解并设置当前的cachename的过期时间 private static final String FULL_PACKAGE_NAME = "com.tohours.bdminiapp";
/** * 配置redisTemplate针对不同key和value场景下不同序列化的方式 * * @param factory Redis连接工厂 * @return */ @Primary @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer<Object> jackson2RedisSerializer = jackson2JsonRedisSerializer(); // 默认使用jdk序列化, template.setDefaultSerializer(jackson2RedisSerializer); return template;
}
// 配置Jackson2JsonRedisSerializer // 避免出现获取缓存时出现的类型转换错误 @SuppressWarnings("deprecation") private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>( Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be // cast to XXX objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 日期格式化 objectMapper.registerModule(new Jdk8Module()).registerModule(new JavaTimeModule()) .registerModule(new ParameterNamesModule());
return jackson2JsonRedisSerializer; }
@Bean RedisCache cache(RedisTemplate<String, Object> redisTemplate) { return new com.tohours.bdminiapp.config.redis.RedisCacheManager(redisTemplate); }
/** * 自定义cacheManager,为了实现@cacheable针对cachaName整个层的过期时间,而不能单独指定一个key的过期时间,如果想单独指定 * 一个key的过期时间,则将这个key独立一层。 写法如下: * * @Cacheable(key = * "'1000'+#root.methodName+'_'+#orgCode",cacheNames="queryOrg") * 其中前面的1000是过期时间,单位是s,cacheNames就是自定义的名称,如果key开头不是数字#则使用默认的spring.cache.redis.time-to-live=180s * */ @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), // 默认策略,未配置的 key 会使用这个 this.defaultCacheConfiguration(timeToLive / 1000), // 指定 key 策略 this.getRedisCacheConfigurationMap()); }
/** * @desc 默认redicCache的配置 * @param seconds * @return */ private RedisCacheConfiguration defaultCacheConfiguration(Integer seconds) { return RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())) .entryTtl(Duration.ofSeconds(seconds)); }
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() { Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(); // 通过反射工具获取到需要扫描的包下的所有的class validAnnotation(loadClasses(FULL_PACKAGE_NAME), redisCacheConfigurationMap); return redisCacheConfigurationMap; }
@SuppressWarnings("rawtypes") public static List<Class> loadClasses(String packageName) { List<Class> classes = new ArrayList<>(); String path = packageName.replace(".", "/"); File dir = new File(Thread.currentThread().getContextClassLoader().getResource(path).getFile()); if (dir.exists()) { for (File file : dir.listFiles()) { String fileName = file.getName(); if (file.isDirectory()) { List<Class> subClasses = loadClasses(packageName + "." + fileName); classes.addAll(subClasses); } else if (fileName.endsWith(".class")) { String className = fileName.substring(0, fileName.length() - 6); try { classes.add(Class.forName(packageName + "." + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } return classes; }
@SuppressWarnings("rawtypes") private void validAnnotation(List<Class> clsList, Map<String, RedisCacheConfiguration> redisCacheConfigurationMap) { if (CollUtil.isEmpty(clsList)) { return; } clsList.forEach(cls -> { // 1.0获取所有的方法 Method[] methods = cls.getDeclaredMethods(); if (Objects.isNull(methods)) { return;// 如果类中无方法则返回 }
for (Method method : methods) { // 2.0 获取方法上的@Cacheable注解 Cacheable cacheable = method.getAnnotation(Cacheable.class); if (Objects.isNull(cacheable)) { continue;// 如果方法无此注解则默认不做处理 } // 3.0 按照定义好的规则截取出过期时间, 并进行设置 String[] split = cacheable.key().replace("'", "").replace("+", "").split("#"); if (split.length >= 2) { Boolean isInt = isInt(split[0]);// 定义的规则是开头是时间,如果是int类型则走后续的定义失效时间的规则 if (isInt) { try { String[] methodCacheNames = cacheable.cacheNames(); if (methodCacheNames != null && methodCacheNames.length > 0) { Arrays.stream(methodCacheNames).forEach(v -> redisCacheConfigurationMap.put(v, this.defaultCacheConfiguration(Integer.valueOf(split[0])))); }
} catch (Exception e) { log.error("自定义SpringCache过期时间异常", e); }
}
} } }); }
private Boolean isInt(String str) { try { Integer.valueOf(str); } catch (Exception e) { return false; } return true; } }
|