From 989ddf75520b737c1f476aeb966d499c98206aea Mon Sep 17 00:00:00 2001 From: seakider Date: Sat, 4 Jan 2025 19:50:45 +0800 Subject: [PATCH 1/2] Feature - add MapEntryListener in RedissonSpringCacheManager Signed-off-by: seakider --- .../redisson/spring/cache/CacheConfig.java | 24 +++++++++++++++ .../spring/cache/CacheConfigSupport.java | 29 ++++++++++++++----- .../cache/RedissonSpringCacheManager.java | 4 +++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/redisson/src/main/java/org/redisson/spring/cache/CacheConfig.java b/redisson/src/main/java/org/redisson/spring/cache/CacheConfig.java index 5149bdd63..5da1df725 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/CacheConfig.java +++ b/redisson/src/main/java/org/redisson/spring/cache/CacheConfig.java @@ -15,11 +15,15 @@ */ package org.redisson.spring.cache; +import org.redisson.api.map.event.MapEntryListener; + import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -35,6 +39,8 @@ public class CacheConfig { private long maxIdleTime; private int maxSize; + + private final List listeners = new ArrayList<>(); /** * Creates config object with @@ -103,6 +109,24 @@ public class CacheConfig { this.maxIdleTime = maxIdleTime; } + /** + * listener will invoke if one of the ttl,maxIdleTime,maxSize is set + * listener Is one of the following implementations: + * EntryCreatedListener + * EntryExpiredListener + * EntryRemovedListener + * EntryUpdatedListener + * + * @param listener listener + */ + public void addListener(MapEntryListener listener) { + listeners.add(listener); + } + + protected List getListeners() { + return listeners; + } + /** * Read config objects stored in JSON format from String * diff --git a/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java b/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java index d84026255..a86ad8692 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java +++ b/redisson/src/main/java/org/redisson/spring/cache/CacheConfigSupport.java @@ -15,6 +15,14 @@ */ package org.redisson.spring.cache; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.redisson.api.map.event.MapEntryListener; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -22,10 +30,6 @@ import java.io.Reader; import java.net.URL; import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; - /** * * @author Nikita Koksharov @@ -33,9 +37,20 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; */ public class CacheConfigSupport { - ObjectMapper jsonMapper = new ObjectMapper(); - ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); - + ObjectMapper jsonMapper = createMapper(null); + ObjectMapper yamlMapper = createMapper(new YAMLFactory()); + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "class") + public static class ClassMixIn { + } + + private ObjectMapper createMapper(JsonFactory mapping) { + ObjectMapper mapper = new ObjectMapper(mapping); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.addMixIn(MapEntryListener.class, ClassMixIn.class); + return mapper; + } + public Map fromJSON(String content) throws IOException { return jsonMapper.readValue(content, new TypeReference>() {}); } diff --git a/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java b/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java index 9079760e2..9337dc0a2 100644 --- a/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java +++ b/redisson/src/main/java/org/redisson/spring/cache/RedissonSpringCacheManager.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.redisson.api.RMap; import org.redisson.api.RMapCache; import org.redisson.api.RedissonClient; +import org.redisson.api.map.event.MapEntryListener; import org.redisson.client.codec.Codec; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.InitializingBean; @@ -269,6 +270,9 @@ public class RedissonSpringCacheManager implements CacheManager, ResourceLoaderA cache = oldCache; } else { map.setMaxSize(config.getMaxSize()); + for (MapEntryListener listener : config.getListeners()) { + map.addListener(listener); + } } return cache; } From 2cd38cf99758b67e1a2e96a5e110c1298e07678c Mon Sep 17 00:00:00 2001 From: seakider Date: Sat, 4 Jan 2025 19:51:43 +0800 Subject: [PATCH 2/2] Feature - add MapEntryListener in RedissonSpringCacheManager, test case Signed-off-by: seakider --- .../RedissonSpringCacheShortTTLTest.java | 46 +++++++++++++++++-- .../spring/cache/cache-config-shortTTL.json | 2 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java b/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java index dd22e52ca..ecf570f61 100644 --- a/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java +++ b/redisson/src/test/java/org/redisson/spring/cache/RedissonSpringCacheShortTTLTest.java @@ -5,7 +5,11 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.redisson.RedisDockerTest; +import org.redisson.Redisson; import org.redisson.api.RedissonClient; +import org.redisson.api.map.event.EntryEvent; +import org.redisson.api.map.event.EntryExpiredListener; +import org.redisson.config.Config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; @@ -23,6 +27,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -94,13 +99,15 @@ public class RedissonSpringCacheShortTTLTest extends RedisDockerTest { @Bean(destroyMethod = "shutdown") RedissonClient redisson() { - return createInstance(); + return createRedisson(); } @Bean CacheManager cacheManager(RedissonClient redissonClient) throws IOException { Map config = new HashMap(); - config.put("testMap", new CacheConfig(1 * 1000, 1 * 1000)); + CacheConfig cacheConfig = new CacheConfig(1 * 1000, 1 * 1000); + cacheConfig.addListener(new SimpleExpireListener()); + config.put("testMap", cacheConfig); return new RedissonSpringCacheManager(redissonClient, config); } @@ -113,7 +120,7 @@ public class RedissonSpringCacheShortTTLTest extends RedisDockerTest { @Bean(destroyMethod = "shutdown") RedissonClient redisson() { - return createInstance(); + return createRedisson(); } @Bean @@ -122,8 +129,28 @@ public class RedissonSpringCacheShortTTLTest extends RedisDockerTest { } } - + + public static class SimpleExpireListener implements EntryExpiredListener { + @Override + public void onExpired(EntryEvent event) { + counter.computeIfAbsent(event.getKey(), k -> { + AtomicInteger ac = new AtomicInteger(); + ac.incrementAndGet(); + return ac; + }); + } + } + + private static RedissonClient createRedisson() { + Config config = createConfig(); + // fix evict time + config.setMinCleanUpDelay(1); + config.setMaxCleanUpDelay(1); + return Redisson.create(config); + } + private static Map, AnnotationConfigApplicationContext> contexts; + private static final Map counter = new HashMap<>(); public static List> data() { return Arrays.asList(Application.class, JsonConfigApplication.class); @@ -165,5 +192,16 @@ public class RedissonSpringCacheShortTTLTest extends RedisDockerTest { bean.read("object1"); }); } + + @ParameterizedTest + @MethodSource("data") + public void testListener(Class contextClass) throws InterruptedException { + AnnotationConfigApplicationContext context = contexts.get(contextClass); + SampleBean bean = context.getBean(SampleBean.class); + bean.store(contextClass.getName(), new SampleObject("name1", "value1")); + + Thread.sleep(5000); + assertThat(counter.get(contextClass.getName()).get()).isEqualTo(1); + } } diff --git a/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json b/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json index 44fc9a7e1..45e0374df 100644 --- a/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json +++ b/redisson/src/test/resources/org/redisson/spring/cache/cache-config-shortTTL.json @@ -1 +1 @@ -{"testMap":{"ttl":1000,"maxIdleTime":1000}} \ No newline at end of file +{"testMap":{"ttl":1000,"maxIdleTime":1000,"listeners": [{"class": "org.redisson.spring.cache.RedissonSpringCacheShortTTLTest$SimpleExpireListener"}]}} \ No newline at end of file