Spring cache dynamically obtains the key under the redis specified namespace

Spring cache is a component that can implement caching through annotations or XML configuration. It is very convenient in the development process, such as @Cacheable, @CacheEvict, etc. Generally, you add comments to the method in advance, and the cacheName is hard-coded, such as the following example:

    @Cacheable(value = "oxx.applications", key = "#packageName")
    public Application fetchByPackageName(String packageName) {
        Application application = new Application();
        application.setPackageName(packageName);
        Application application1 = applicationMapper.selectOne(application);
        return application1;
    }

In this way, as long as you call a method, you will go to the namespace as oxx.applications to find the redis key value with the key oxx.applications:+packageName (PS: RedisCacheManager is used here) The redis storage structure is as follows:

Insert picture description here
But one day there was a problem in the production environment. I suspected that it was a caching problem, so I wanted to see the value of a certain key in redis. At this time, I can only find a DBA. It doesn't matter once or twice, but I often have to buy Coke. So I want to make a backdoor (make an interface, enter the key in the front end to get the cache).
Just do it, think it's simple, isn't it just writing a Controller, simple. . . But with so many keys, the namespace (cacheName) of each key is different, is it necessary to write N interfaces?

    @Cacheable(value = "oxx.applications1", key = "#packageName")
    public Application fetchByPackageName(String packageName) {
        return null;
    }
    
    @Cacheable(value = "oxx.applications2", key = "#key2")
    public Application fetchByKey2(String key2) {
        return null;
    }
    
    @Cacheable(value = "oxx.applications3", key = "#key3")
    public Application fetchByKey3(String key3) {
        return null;
    }
    ..........
    ..........
    ..........
    ..........
    ..........
    ..........

For example? This will definitely not work.

Click on the @Cacheable annotation, there is a cacheResolver in it. The literal meaning is cache resolution. I thought about whether I can use this to make an article, so I checked the Api document and found that it is feasible.
The NamedCacheResolver class implements the CacheResolver interface, which contains the cacheNames collection and is associated with the CacheManager object, so it supports modifying cahceName by calling methods.

Idea: You can pass in cahceName and key when calling the get cache interface, dynamically modify the cacheNames property of CacheManager every time, and finally call the method annotated with @Cacheable.

code show as below:

package com.xxxxx.xxxx.xxx.xx.xx.cache;

import com.boot.service.cache.manager.CustomizerRedisCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author 
 */
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
@Service
public class DynamicRedisCache {

    @Cacheable(cacheResolver = "namedCacheResolver", key = "#key")
    public Object get(String key){
        // 只查不覆盖,所以return null,redis默认不会存为null的key
        return null;
    }

    @CacheEvict(cacheResolver = "namedCacheResolver", key = "#key")
    public void del(String key) {
    }

    @Bean
    public CustomizerRedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {

        CustomizerRedisCacheManager redisCacheManager = new CustomizerRedisCacheManager(redisTemplate);
        redisCacheManager.setUsePrefix(true);

        return redisCacheManager;

    }

    @Bean
    public NamedCacheResolver namedCacheResolver(RedisCacheManager redisCacheManager){
        NamedCacheResolver namedCacheResolver = new NamedCacheResolver(redisCacheManager);
        namedCacheResolver.setCacheNames(redisCacheManager.getCacheNames());
        return namedCacheResolver;
    }
}

RedisCacheManager


import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * @Description RedisCacheManager 覆盖父类的loadCaches方法,父类每次会保留上一次的cacheName
 * @Author 
 * @Date 2019-07-04 09:34
 **/
public class CustomizerRedisCacheManager extends RedisCacheManager {
    private Set<String> configuredCacheNames;

    public CustomizerRedisCacheManager(RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    public void setCacheNames(Collection<String> cacheNames) {
        Set<String> newCacheNames = CollectionUtils.isEmpty(cacheNames) ? Collections.emptySet() : new HashSet(cacheNames);
        this.configuredCacheNames = newCacheNames;
    }

    @Override
    protected Collection<? extends Cache> loadCaches() {
        Set<Cache> caches = new LinkedHashSet(new ArrayList());
        Set<String> cachesToLoad = new LinkedHashSet(this.configuredCacheNames);
        if (!CollectionUtils.isEmpty(cachesToLoad)) {
            Iterator var3 = cachesToLoad.iterator();

            while(var3.hasNext()) {
                String cacheName = (String)var3.next();
                caches.add(this.createCache(cacheName));
            }
        }

        return caches;
    }
}

RedisTemplate

 @Bean
 public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

Note: @ConditionalOnProperty added this annotation because spring.cache.type = simple is used in the unit test. If you do not add this annotation, the unit test will fail -_- If there is no unit test or redis is used, just remove it.
redisCacheManager.setUsePrefix(true);-It means that the key contains a prefix, which usually contains a prefix, so it is necessary.
CustomizerRedisCacheManager is my custom class, which inherits RedisCacheManager. Because each request caches the last cacheName, it rewrites the loadCaches of the parent class to ensure that it is the current cacheName every time.

With two instances of NamedCacheResolver and CustomizerRedisCacheManager, we can dynamically obtain the cache by dynamically modifying the cacheName when calling the cache interface.

import com.google.common.collect.Lists;
import com.service.cache.manager.CustomizerRedisCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.stereotype.Service;

/**
 * @author 
 */
@Service
public class CacheService {

    @Autowired(required = false)
    private DynamicRedisCache dynamicRedisCache;

    @Autowired(required = false)
    private CustomizerRedisCacheManager redisCacheManager;

    @Autowired(required = false)
    private NamedCacheResolver namedCacheResolver;

    /**
     * 动态获取spring cache cacheName 对应key的redis值
     * @param cacheName
     * @param key
     * @return
     */
    public Object getFromRedis(String cacheName, String key) {
        setCacheResolver(cacheName);
        return dynamicRedisCache.get(key);
    }

    /**
     * 删除redis指定namespace的键值
     * @param cacheName
     * @param key
     */
    public boolean delRedisCache(String cacheName, String key){
        setCacheResolver(cacheName);
        dynamicRedisCache.del(key);
        return true;
    }

    private void setCacheResolver(String cacheName){
        redisCacheManager.setCacheNames(Lists.newArrayList(cacheName));
        redisCacheManager.afterPropertiesSet();
        namedCacheResolver.setCacheNames(redisCacheManager.getCacheNames());
        namedCacheResolver.setCacheManager(redisCacheManager);
    }
}

Controller

    @Autowired
    private CacheService cacheService;

    @GetMapping("/getFromRedis")
    public CommonResponse getFromRedis(@Param("cacheName") String cacheName, @Param("cacheKey") String cacheKey) {
        return Response.ok("获取成功", cacheService.getFromRedis(cacheName, cacheKey));
    }
    
   @GetMapping("/del")
    public CommonResponse delRedisCache(@Param("cacheName") String cacheName, @Param("cacheKey") String cacheKey) {
        return Response.ok("删除指定缓存成功", cacheService.delRedisCache(cacheName, cacheKey));
    }

That's it.
Insert picture description here

Insert picture description here

Guess you like

Origin blog.csdn.net/huangdi1309/article/details/94739602