Spring boot redis cache sets the effective time and auto refresh cache, and the auto refresh time is set in the annotation.
preface
This article mainly supplements the spring redis cache used by small partners. Spring redis cache is a general solution. Although spring redis cache has been very powerful, it still can't cope with programmers' strange ideas. All right, I've finished talking. Now I'm going to start to move.
problem
When we use spring redis cache for caching, we often set a refresh lead. For example, you set the cache expiration period to five minutes, but you want to refresh the cache in the background in the last two minutes, and it's asynchronous, so the front end gets the cached data, but after that, the cache starts to refresh asynchronously to ensure that the next request gets the latest data. There are many ways to realize this function. All the project groups I see are implemented in code. As a minimalist, I can't go any further. This article uses the @ Cacheable annotation of spinning redis cache to realize the asynchronous refresh redis cache function and can set the refresh time @ Cacheable(value = "KKK ා 60", key = "ා map") in the annotation. Many posts on the Internet are available in a single version. This film is applicable to all spring data redis versions.
Let's see the effect first
// Refresh time 60 seconds @Cacheable(value="resultMap#60",key="#params.toString()) public Map<String,Object> getName(Map<String,String params){ Map<String,Object> resultMap = new HashMap<>(); ...... ...... ...... return resultMap; }
Configure redisConfig
This file mainly configures redis, such as serialization mode, connection pool, and cache expiration time.
@EnableCaching //Open cache @Configuration @ConditionalOnClass(RedisOperations.class) public class RedisConfig{ @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){ RedisTemplate<String,Object> template = new RedisTemplate<>(); //Configure connection factory template.setConnectionFactoty(factory); //Serializing and deserializing redis value values (JDK serialization is used by default) Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper omm = new ObjectMapper(); //Specify serialization domain omm.setVisibility(PropertyAccessor.All,JsonAutoDetect.Visibility.ANY); //Specify serialization input type omm.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(omm); //Value is serialized in json template.setValueSerializer(jacksonSeial); //Use StringRedisSerializer to serialize and deserialize the key value of redis template.setKeySerializer(new StringRedisSerializer()); //Set hash key and value serialization mode template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } /** * json serialize * @return */ @Bean public RedisSerializer<Object> jackson2JsonRedisSerializer() { //Use Jackson 2jsonredisserializer to serialize and deserialize the value of redis Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); return serializer; } /** * Configure cache manager * @param redisConnectionFactory * @return */ @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // Generate a default configuration, and customize the cache configuration through the config object RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // Set the default expiration time of the cache, and also use the Duration setting config = config.entryTtl(Duration.ofMinutes(1)) // Set key to string serialization .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // Set value to json serialization .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())) // Do not cache null values .disableCachingNullValues(); // Set an initial set of cache spaces Set<String> cacheNames = new HashSet<>(); cacheNames.add("timeGroup"); cacheNames.add("user"); // Apply different configurations to each cache space Map<String, RedisCacheConfiguration> configMap = new HashMap<>(); configMap.put("timeGroup", config); configMap.put("user", config.entryTtl(Duration.ofSeconds(120))); // Initializing a cacheManager with a custom cache configuration RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory)) // First call this method to set the initialized cache name, and then initialize the related configuration .initialCacheNames(cacheNames) .withInitialCacheConfigurations(configMap) .cacheDefaults(config).transactionAware() .build(); return cacheManager; } /** * The cached key is package name + method name + parameter list */ @Bean public KeyGenerator keyGenerator() { return (target, method, objects) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append("::" + method.getName() + ":"); for (Object obj : objects) { sb.append(obj.toString()); } return sb.toString(); }; }
CacheAspect.java
When the request comes in, scan @ Cacheable annotation on the method, it will trigger the blocker of cacheevent cache to parse and take value for the annotation. Asynchronous refresh.
@Order(0) //Priority, lower value, higher priority @Aspect //section @Component public class CacheAspect { private static final Logger LOGGER = LoggerFactory.getLogger(CacheAspect.class); @Autowired private CacheSupport cacheSupport; @Autowired private RedisTemplate redisTemplate; @Autowired private AsynchronousFunction asynchronousFunction; @Pointcut("@annotation(org.springframework.cache.annotation.Cacheable") public void pointCut(){} @Around("pointCut()") public Object registerInvoke(ProceedingJoinPoint joinPoint) throws Throwable{ Object[] args = joinPoint.getArgs(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); Object target = joinPoint.getTarget(); Method methodJ = this.getSpecificmethod(joinPoint); //Parsing Cacheable List<Cacheable> annotations = this.getMethodAnnotations(methodJ,Cacheable.class); Set<String> cacheSet = new HashSet<>(); String cacheKeyJ = null; for(Cacheable cacheables : annotations){ cacheSet.addAll(Arrays.asList(cacheables.value())); cacheKeyJ = cacheables.key(); } if(joinPoint.getSignature() instanceof MethodSignature){ Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes,joinPoint.getArgs(),cacheSet,cacheKeyJ); cacheKeyJ = key.toString(); } Iterator<String> it = cacheSet.iterator(); String valueKey = null; while(it.hasNext()){ valueKey = it.next(); } //Get expiration threshold String[] split = valueKey.split("#"); long threshold = Long.parseLong(split[1]); String methodInvokeKey = valueKey+"::"+cacheKeyJ; //Write the method executor to redis, then get the executor from redis when you need to refresh, and then refresh the cache according to cacheKey Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target); MethodInvoker methodInvoker = new MethodInvoker(); methodInvoker.setTargetClass(targetClass); methodInvoker.setTargetMethod(method.getName()); methodInvoker.setArguments(args); Long expire = redisTemplate.opsForValue().getOperations().getExpire(methodInvokeKey); if((expire != -1 && expire != -2) && expire <= threshold){ logger.info("Start asynchronous refresh redis Cache!"); asynchronousFunction.refreshCache(methodInvokeKey,methodInvoker); } Object proceed = joinPoint.proceed(); return proceed; } private <T extends Annotation> List<T> getMethodAnnotations(AnnotatedElement ae, Class<T> annotationType) { List<T> anns = new ArrayList<T>(2); // look for raw annotation T ann = ae.getAnnotation(annotationType); if (ann != null) { anns.add(ann); } // look for meta-annotations for (Annotation metaAnn : ae.getAnnotations()) { ann = metaAnn.annotationType().getAnnotation(annotationType); if (ann != null) { anns.add(ann); } } return (anns.isEmpty() ? null : anns); } private Method getSpecificmethod(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method method = methodSignature.getMethod(); // The method may be on an interface, but we need attributes from the // target class. If the target class is null, the method will be // unchanged. Class<?> targetClass = AopProxyUtils.ultimateTargetClass(pjp.getTarget()); if (targetClass == null && pjp.getTarget() != null) { targetClass = pjp.getTarget().getClass(); } Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); // If we are dealing with method with generic parameters, find the // original method. specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); return specificMethod; }
AsynchronousFunction.java
A method to update the cache asynchronously.
@Component public class AsychronousFunction{ private static final Logger logger = LoggerFactory.getLogger(AsychronousFunction.class); @Autowired private ApplicationContext applicationContext; private final long EXPIRE_SECONDS = 5; @Autowired private DistributedLock distributeLock; private String lockUuid = "kshdfsjdfhsfs43hhj34khj33jkj_"; /** *Refresh cache *@param cacheKey *@Param methodInvoker *return **/ @Async public void refreshCache(String cacheKey,MethodInvoker methodInvoker){ String methodInvokeKey = cacheKey; if(methodInvoker != null){ Class<?> targetClass = methodInvoker.getTargetClass(); Object target = AopProxyUtils.getSingletonTarget(applicationContext.getBean(targetClass)); methodInvoker.setTargetObject(target); //Shackles (distributed locks) if(!distributedLock.getLock(DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_TIME) == false){ try{ methodInvoker.prepare(); Object invoke = methodInvoker.invoke(); //Then set the cache and reset the expiration time redisTemplate.opsForValue().set(cacheKey,invoke); redisTemplate.expire(cacheKey,EXPIRE_SECONDS,TimeUnit.MINUTES); } catch(InvocationTargetException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException e){ logger.error("Refresh cache failed:"+e.getMessage(),e); } finally { distributedLock.releaseLock(DistributedLock.REDIS_LOCK_KEY+lockUuid,DistributedLock.REDIS_LOCK_KEY+lockUuid); } } } } }
epilogue
This is the extension of cache @ Cacheable annotation. The asynchronous refresh function of redis cache is realized by annotation. Here is the source code of github project.
Link: github
Picture:
Reference article: https://www.jianshu.com/p/275cb42080d9