As an important means to improve application performance, the rationality of its management strategy directly affects the application's response speed and data consistency. In the Spring framework, Spring Cache provides a declarative caching solution, and Redis is widely used in cache implementations as a high-performance cache database. This article will introduce a strategy to implement Spring Cache and Redis cache expiration time management and automatic refresh through custom annotations.
1. Custom annotation CacheExpireConfig
To control the expiration time of the cache more flexibly, we defined a name calledCacheExpireConfig
Custom annotations for . This annotation supports configuring cache expiration time and automatic refresh time at the method level.
import .*; /** * @author tangzx */ @Target({, }) @Retention() @Inherited @Documented public @interface CacheExpireConfig { /** * The cache expiration time is supported, and the unit days (d), hours (h), minutes (m), seconds (s) is not filled in the default seconds) * Example: 2h */ String expireTime() default ""; /** * The cache expired refresh time supports unit days (d), hours (h), minutes (m), seconds (s) (no unit default seconds) * Example: 2h */ String expireRefreshTime() default ""; }
2. Use notes
In Spring@Cacheable
Based on the annotation,@CacheExpireConfig
Annotation, we can easily set cache expiration and refresh policies for specific methods.
@Override @CacheExpireConfig(expireTime = "60s", expireRefreshTime = "30s") @Cacheable(value = "testCache", condition = "#userId != null && #userName == null ") public String testCache(String userId, String userName) { ("=====================>"); return "success"; }
3. Load cache expired configuration at startup
When Spring Boot app starts,TaRedisCacheConfigListener
Listener, scans all classes and methods, loads with@CacheExpireConfig
Annotated cache expired configuration of methods.
import ; import .; import ; import ; import ; import ; import ; /** * @author tangzx * @date 2022/12/17 11:05 */ public class TaRedisCacheConfigListener implements ApplicationListener<ApplicationPreparedEvent> { @Override public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) { // Scan all classes Set<Class<?>> classes = scanPackage(); for (Class<?> target : classes) { Method[] methods = (); for (Method method : methods) { // If @Cacheable and @CacheExpireConfig are not annotated at the same time on the method, no configuration is required if (!() || !()) { continue; } Cacheable cacheable = (); CacheExpireConfig cacheExpireConfig = (); String expireTime = (); String expireRefreshTime = (); String[] cacheNames = ((), ()); boolean autoRefresh = (); for (String cacheName : cacheNames) { MethodCacheExpireConfig methodCacheExpireConfig = () .expireTime((expireTime).getSeconds()) .expireRefreshTime((expireRefreshTime).getSeconds()) .autoRefresh(autoRefresh) .target(target) .method(method) .build(); (cacheName, methodCacheExpireConfig); } } } } private Set<Class<?>> scanPackage() { // If the hutool class scanner is used, you can implement it yourself if the tool class is not used in the project return (); } }
public static void main(String[] args) { SpringApplication application = new SpringApplicationBuilder().sources().build(args); try { (new TaRedisCacheConfigListener()); (args); } catch (Exception e) { (); } }
4. Rewrite RedisCacheManager and set the expiration time
By rewriteRedisCacheManager
, we can dynamically set the expiration time of each cache according to the configuration.
import org.; import org.; import ; import ; import ; import ; import ; import ; import ; import ; /** * @author Tzx * @date 2022/12/13 19:33 */ public class TaRedisCacheManager extends RedisCacheManager { private static final Logger LOGGER = (); public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { super(cacheWriter, defaultCacheConfiguration); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, initialCacheNames); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation); } @Override protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) { MethodCacheExpireConfig cacheable = (name); if (null != cacheable && () > 0) { cacheConfig = entryTtl(name, (), cacheConfig); } return (name, cacheConfig); } private RedisCacheConfiguration entryTtl(String cacheName, long ttl, @Nullable RedisCacheConfiguration cacheConfig) { (cacheConfig, "RedisCacheConfiguration is required; it must not be null"); cacheConfig = ((ttl)); if (()) { ("redisCache {} Expiration time is{}Second", cacheName, ttl); } return cacheConfig; } }
5. Cache refresh automatically
existRedisCache
ofget
In the method, if the cache has not expired, check whether it needs to be automatically refreshed.
@Override public ValueWrapper get(@Nullable Object o) { if (null == o) { return null; } ValueWrapper wrapper = (o); // Refresh the cache if (null != wrapper) { ().getBean().refreshCache(getName(),(), this::put); } return wrapper; }
6. TaRedisCacheFactory refresh strategy
TaRedisCacheFactory
Responsible for the refresh logic of the cache to ensure the real-timeness of the cached data.
import ; import org.; import org.; import ; import ; import ; import ; import ; import ; /** * @author tangzx * @date 2022/12/17 11:09 */ public class TaRedisCacheFactory { /** * Cache expired configuration */ private static final ConcurrentHashMap<String, MethodCacheExpireConfig> CACHE_EXPIRE_CONFIG = new ConcurrentHashMap<>(); private static final Logger LOGGER = (); public TaRedisCacheFactory() { // document why this method is empty } public static void addCacheExpireConfig(String cacheName, MethodCacheExpireConfig methodCacheExpireConfig) { CACHE_EXPIRE_CONFIG.put(cacheName, methodCacheExpireConfig); } public static MethodCacheExpireConfig getCacheExpireConfig(String cacheName) { return CACHE_EXPIRE_CONFIG.get(cacheName); } /** * Refresh cache * * @param cacheName cacheName * @param cacheKey cache key */ public void refreshCache(String cacheName, String cacheKey, RefreshCacheFunction f) { MethodCacheExpireConfig cacheable = getCacheExpireConfig(cacheName); if (null == cacheable) { return; } Class<?> targetClass = (); Method method = (); long expireRefreshTime = (); String redisKey = cacheName + cacheKey; long expire = (redisKey); if (expire > expireRefreshTime) { return; } String argsStr = ("\\^")[1]; Object[] args = (argsStr, Object[].class); if (null == args) { return; } try { // Create method executor MethodInvoker methodInvoker = new MethodInvoker(); (args); (targetClass); (()); ((().getBean(targetClass))); (); Object invoke = (); // Then set the cache and reset the expiration time (cacheKey, invoke); (cacheKey, (), ); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { ("Failed to refresh the cache:" + (), e); } } }
7、MethodCacheExpireConfig
import ; import ; import ; /** * @author Tzx * @date 2022/12/17 11:10 */ @Data @Builder public class MethodCacheExpireConfig { /** * cache expiration time */ private long expireTime; /** * The cache expires automatically refreshes the threshold */ private long expireRefreshTime; /** * Whether to refresh automatically */ private boolean autoRefresh; /** * Class object */ private Class<?> target; /** * Cache method */ private Method method; }
8、RefreshCacheFunction
/** * @author tangzx */ @FunctionalInterface public interface RefreshCacheFunction { /** * Cache put * * @param key key * @param value value */ void put(String key, Object value); }
9、DurationUtils
import ; /** * @author Tzx * @date 2022/12/17 12:04 */ public class DurationUtils { private DurationUtils(){ // 2022/12/18 } public static Duration parseDuration(String ttlStr) { String timeUnit = (() - 1); switch (timeUnit) { case "d": return (parseLong(ttlStr)); case "h": return (parseLong(ttlStr)); case "m": return (parseLong(ttlStr)); case "s": return (parseLong(ttlStr)); default: return ((ttlStr)); } } private static long parseLong(String ttlStr) { return ((0, () - 1)); } }
This is the article about SpirngCache, Redis specified expiration time, and automatic refresh of expiration. For more related SpirngCache, Redis specified expiration time, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!