Skip to content

Commit c1f23b0

Browse files
committed
reactify-cache
1 parent d864774 commit c1f23b0

File tree

6 files changed

+104
-108
lines changed

6 files changed

+104
-108
lines changed

reactify-cache/src/main/java/com/reactify/CacheAspect.java

+2-11
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,14 @@ public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
7878
try {
7979
Object result = joinPoint.proceed(args);
8080
if (!(result instanceof Mono<?>)) {
81-
log.error(
82-
"Method {}.{} must return a Mono<?> but got: {}",
83-
joinPoint.getTarget().getClass().getSimpleName(),
84-
joinPoint.getSignature().getName(),
85-
result.getClass().getSimpleName());
81+
log.warn("Method {} must return a Mono<?> but got: {}", nameCache, result.getClass().getSimpleName());
8682
return Mono.error(new IllegalStateException("Method must return Mono<?>"));
8783
}
8884
@SuppressWarnings("unchecked")
8985
var resultCast = (Mono<Object>) result;
9086
return resultCast;
9187
} catch (Throwable ex) {
92-
log.error(
93-
"Error executing method: {}.{} - {}",
94-
joinPoint.getTarget().getClass().getSimpleName(),
95-
joinPoint.getSignature().getName(),
96-
ex.getMessage(),
97-
ex);
88+
log.error("Execution error in {} - {}", nameCache, ex.getMessage(), ex);
9889
return Mono.error(ex);
9990
}
10091
}))

reactify-cache/src/main/java/com/reactify/CacheStore.java

+34-29
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@
3737
import java.util.*;
3838

3939
/**
40+
* The {@code CacheStore} class is responsible for managing local caches using Caffeine.
41+
* It automatically initializes caches based on methods annotated with {@link LocalCache},
42+
* supports retrieving caches by name, and provides functionalities to clear specific or all caches.
4043
* <p>
41-
* CacheStore class.
44+
* This class also supports auto-loading caches on application startup and integrates with Spring's
45+
* application context lifecycle events.
4246
* </p>
4347
*
4448
* @author hoangtien2k3
@@ -51,8 +55,13 @@ public class CacheStore implements ApplicationContextAware {
5155
*/
5256
private static final Logger log = LoggerFactory.getLogger(CacheStore.class);
5357

58+
/** Stores the caches mapped by their names. */
5459
private static final HashMap<String, Cache<Object, Object>> caches = new HashMap<>();
60+
61+
/** Stores methods annotated with {@link LocalCache} that require auto-loading. */
5562
private static final Set<Method> autoLoadMethods = new HashSet<>();
63+
64+
/** The base package for scanning cache-related methods. */
5665
private static String reflectionPath;
5766

5867
@PostConstruct
@@ -61,6 +70,7 @@ private static void init() {
6170
Reflections reflections = new Reflections(reflectionPath, Scanners.MethodsAnnotated);
6271
Set<Method> methods =
6372
reflections.get(Scanners.MethodsAnnotated.with(LocalCache.class).as(Method.class));
73+
log.info("Found {} methods annotated with @LocalCache", methods.size());
6474
for (Method method : methods) {
6575
String className = method.getDeclaringClass().getSimpleName();
6676
LocalCache localCache = method.getAnnotation(LocalCache.class);
@@ -115,30 +125,19 @@ public static List<String> getCaches() {
115125
}
116126

117127
/**
118-
* Clear localCache by serviceName
119-
*
120-
* @param serviceName
121-
* String
122-
* @return count of cleared caches
123-
*/
124-
public static int clearCachesInServiceName(String serviceName) {
125-
return (int) caches.entrySet().stream()
126-
.filter(entry -> entry.getKey().contains(serviceName))
127-
.peek(entry -> entry.getValue().invalidateAll())
128-
.count();
129-
}
130-
131-
/**
132-
* clear localCache by cacheName
128+
* Clears the specified cache by name.
133129
*
134-
* @param cacheName
135-
* String
136-
* @return count of cleared caches
130+
* @param cacheName the name of the cache to clear
131+
* @return the number of cleared caches (0 or 1)
137132
*/
138133
public static int clearCachesByName(String cacheName) {
134+
log.info("Clearing cache: {}", cacheName);
139135
return (int) caches.entrySet().stream()
140136
.filter(entry -> entry.getKey().equals(cacheName))
141-
.peek(entry -> entry.getValue().invalidateAll())
137+
.peek(entry -> {
138+
entry.getValue().invalidateAll();
139+
log.info("Cache {} cleared", entry.getKey());
140+
})
142141
.count();
143142
}
144143

@@ -148,36 +147,41 @@ public static int clearCachesByName(String cacheName) {
148147
* @return count of cleared caches
149148
*/
150149
public static int clearAllCaches() {
151-
log.info("Before clearing, cache size: {}", caches.size());
150+
log.info("Clearing all caches");
152151
int count = 0;
153152
for (Map.Entry<String, Cache<Object, Object>> entry : caches.entrySet()) {
154153
count ++;
155154
entry.getValue().invalidateAll();
155+
log.info("Cleared cache: {}", entry.getKey());
156156
}
157-
log.info("After clearing, cleared {} caches", count);
158157
return count;
159158
}
160159

161160
/**
162-
* <p>
163-
* autoLoad.
164-
* </p>
161+
* Automatically loads caches for methods annotated with {@link LocalCache} that support auto-loading.
165162
*
166-
* @param event
167-
* a {@link ContextRefreshedEvent} object
163+
* @param event the application context refresh event
168164
*/
169165
@Async
170166
@EventListener
171167
public void autoLoad(ContextRefreshedEvent event) {
168+
log.info("Received ContextRefreshedEvent: {}", event.getApplicationContext());
172169
if (!autoLoadMethods.isEmpty()) {
173-
log.info("=====> Start auto load {} cache <=====", autoLoadMethods.size());
170+
log.info("=====> Start auto-loading {} caches <=====", autoLoadMethods.size());
174171
for (Method method : autoLoadMethods) {
175172
CacheUtils.invokeMethod(method);
173+
log.info("Auto-loaded cache for method: {}", method.getName());
176174
}
177-
log.info("=====> Finish auto load cache <=====");
175+
log.info("=====> Finished auto-loading caches <=====");
178176
}
179177
}
180178

179+
/**
180+
* Sets the application context and determines the base package for scanning cache methods.
181+
*
182+
* @param applicationContext the application context
183+
* @throws BeansException if an error occurs while setting the context
184+
*/
181185
@Override
182186
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
183187
Class<?> mainApplicationClass = applicationContext
@@ -187,5 +191,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
187191
.next()
188192
.getClass();
189193
reflectionPath = mainApplicationClass.getPackageName();
194+
log.info("Set reflection path for cache scanning: {}", reflectionPath);
190195
}
191196
}

reactify-cache/src/main/java/com/reactify/CacheUtils.java

+12-28
Original file line numberDiff line numberDiff line change
@@ -56,41 +56,25 @@ public class CacheUtils {
5656
* a {@link Method} object representing the method to be invoked
5757
*/
5858
public static void invokeMethod(Method method) {
59-
if (method == null) {
60-
log.warn("Method reference is null. Skipping invocation.");
61-
return;
62-
}
63-
6459
try {
6560
Class<?> declaringClass = method.getDeclaringClass();
6661
Object beanInstance = ApplicationContextProvider.getBean(declaringClass);
6762
Object result = method.invoke(beanInstance);
63+
String methodName = declaringClass.getSimpleName() + "." + method.getName();
6864

69-
if (result instanceof Mono<?>) {
70-
((Mono<?>) result)
71-
.subscribe(
72-
success -> log.debug(
73-
"Successfully executed {}.{}",
74-
declaringClass.getSimpleName(),
75-
method.getName()),
76-
error -> log.error(
77-
"Error executing {}.{}",
78-
declaringClass.getSimpleName(),
79-
method.getName(),
80-
error));
65+
if (result instanceof Mono<?> monoResult) {
66+
monoResult.subscribe(
67+
success -> log.debug("Successfully executed {}", methodName),
68+
error -> log.error("Error executing {}", methodName, error)
69+
);
8170
} else {
82-
log.warn(
83-
"Method {}.{} does not return a Mono<?>. Ensure the cache method is reactive.",
84-
declaringClass.getSimpleName(),
85-
method.getName());
71+
log.warn("Method {} does not return a Mono<?>", methodName);
8672
}
87-
} catch (Exception exception) {
88-
log.error(
89-
"Error when autoload cache {}.{}.{}",
90-
method.getDeclaringClass().getSimpleName(),
91-
method.getName(),
92-
exception.getMessage(),
93-
exception);
73+
} catch (IllegalAccessException e) {
74+
log.error("Access violation when invoking method {}: {}", method.getName(), e.getMessage(), e);
75+
} catch (Exception e) {
76+
log.error("Error when autoload cache {}.{}.{}",
77+
method.getDeclaringClass().getSimpleName(), method.getName(), e.getMessage(), e);
9478
}
9579
}
9680
}

reactify-cache/src/main/java/com/reactify/CustomizeRemovalListener.java

+5-11
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,11 @@ public CustomizeRemovalListener(Method method) {
5555
/** {@inheritDoc} */
5656
@Override
5757
public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause removalCause) {
58-
if (removalCause.wasEvicted()) {
59-
log.info(
60-
"Cache {}.{} was evicted because {}",
61-
method.getDeclaringClass().getSimpleName(),
62-
method.getName(),
63-
removalCause);
64-
try {
65-
CacheUtils.invokeMethod(method);
66-
} catch (Exception e) {
67-
log.error("Error invoking method {} on cache eviction", method.getName(), e);
68-
}
58+
if (!removalCause.wasEvicted()) {
59+
return;
6960
}
61+
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
62+
log.info("Cache {} was evicted due to {}", methodName, removalCause);
63+
CacheUtils.invokeMethod(method);
7064
}
7165
}

reactify-cache/src/main/java/com/reactify/LocalCache.java

+51-24
Original file line numberDiff line numberDiff line change
@@ -21,59 +21,86 @@
2121
import java.lang.annotation.Target;
2222

2323
/**
24-
* Marks a method for local caching to enhance performance by reducing redundant
25-
* computations or repeated data retrievals from external sources.
24+
* Annotation for enabling local caching on method results to enhance performance
25+
* by reducing redundant computations and minimizing repeated data retrievals from
26+
* external sources such as databases or APIs.
2627
*
2728
* <p>
28-
* This annotation enables flexible caching configurations through the following
29-
* properties:
29+
* This annotation provides a flexible caching mechanism with configurable properties:
3030
* <ul>
31-
* <li><strong>durationInMinute</strong>: Specifies the validity duration of the
32-
* cached result, in minutes.</li>
33-
* <li><strong>maxRecord</strong>: Defines the maximum number of records the
34-
* cache can store.</li>
35-
* <li><strong>autoCache</strong>: A flag that determines whether the caching
36-
* should activate automatically upon method invocation.</li>
31+
* <li><strong>durationInMinute</strong>: Defines the lifespan of cached entries in minutes.</li>
32+
* <li><strong>maxRecord</strong>: Specifies the maximum number of entries that can be stored in the cache.</li>
33+
* <li><strong>autoCache</strong>: Determines whether caching should be automatically applied when the method is invoked.</li>
3734
* </ul>
35+
* </p>
3836
*
39-
* <p>
40-
* This annotation is best applied to methods where caching can notably improve
41-
* efficiency by retaining data that does not require frequent recalculation.
37+
* <h3>Key Benefits:</h3>
38+
* <ul>
39+
* <li>Reduces redundant computations by caching method results.</li>
40+
* <li>Minimizes database or API calls, improving response times.</li>
41+
* <li>Optimizes resource usage in performance-critical applications.</li>
42+
* </ul>
4243
*
43-
* <h3>Example Usage:</h3>
44+
* <h3>Usage Example:</h3>
4445
*
4546
* <pre>
46-
* &#64;LocalCache(durationInMinute = 10, maxRecord = 500, autoCache = true)
47-
* public MyObject fetchData(String param) {
48-
* // Implementation that fetches or computes data
47+
* {@code
48+
* @LocalCache(durationInMinute = 15, maxRecord = 200, autoCache = true)
49+
* public List<User> fetchActiveUsers() {
50+
* // Retrieves a list of active users from the database
51+
* }
4952
* }
5053
* </pre>
5154
*
5255
* <h3>Annotation Properties:</h3>
5356
* <dl>
5457
* <dt><strong>durationInMinute</strong></dt>
55-
* <dd>Defines the cache retention time in minutes. Default is 120 minutes.</dd>
58+
* <dd>Specifies how long (in minutes) the cache entry remains valid. Default is 120 minutes.</dd>
5659
*
5760
* <dt><strong>maxRecord</strong></dt>
58-
* <dd>Limits the number of records that can be cached simultaneously. Default
59-
* is 1000 entries.</dd>
61+
* <dd>Limits the number of records stored in the cache at any given time. Default is 1000 entries.</dd>
6062
*
6163
* <dt><strong>autoCache</strong></dt>
62-
* <dd>When set to <code>true</code>, the method result is automatically cached
63-
* on execution. Default is <code>false</code>.</dd>
64+
* <dd>If set to <code>true</code>, caching is applied automatically whenever the method is executed. Default is <code>false</code>.</dd>
6465
* </dl>
6566
*
67+
* <h3>Best Practices:</h3>
68+
* <ul>
69+
* <li>Use on methods that return frequently accessed and computationally expensive results.</li>
70+
* <li>Avoid applying to methods with frequently changing data to prevent stale cache issues.</li>
71+
* <li>Adjust `durationInMinute` and `maxRecord` according to system load and data update frequency.</li>
72+
* </ul>
73+
*
6674
* <p>
67-
* This annotation is ideal for performance-critical applications aiming to
68-
* reduce latency and optimize resource usage through local caching.
75+
* This annotation is particularly useful in microservices and high-performance applications
76+
* where minimizing latency and optimizing resource utilization are crucial.
6977
* </p>
7078
*/
7179
@Target(ElementType.METHOD)
7280
@Retention(RetentionPolicy.RUNTIME)
7381
public @interface LocalCache {
82+
83+
/**
84+
* Defines the duration (in minutes) for which a cached entry remains valid.
85+
* After this time, the entry will expire and be removed from the cache.
86+
*
87+
* @return cache duration in minutes (default: 120)
88+
*/
7489
int durationInMinute() default 120;
7590

91+
/**
92+
* Specifies the maximum number of records that can be stored in the cache.
93+
* Once this limit is reached, older entries may be evicted based on cache policies.
94+
*
95+
* @return maximum cache size (default: 1000)
96+
*/
7697
int maxRecord() default 1000;
7798

99+
/**
100+
* Indicates whether caching should be automatically applied when the method is invoked.
101+
* If enabled, the method execution result will be stored in the cache for subsequent calls.
102+
*
103+
* @return <code>true</code> to enable automatic caching, <code>false</code> otherwise (default: false)
104+
*/
78105
boolean autoCache() default false;
79106
}

reactify-test/src/main/java/com/reactify/test/service/StudentService.java

-5
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ public Mono<Integer> clearCacheByName(LocalCacheRequest request) {
6969

7070
return Mono.defer(() -> {
7171
try {
72-
if ("SERVICE_LEVEL".equals(type)) {
73-
int count = CacheStore.clearCachesInServiceName(reflectionPath + "." + nameCache);
74-
return Mono.just(count);
75-
}
76-
7772
if ("METHOD_LEVEL".equals(type)) {
7873
int count = CacheStore.clearCachesByName(nameCache);
7974
return Mono.just(count);

0 commit comments

Comments
 (0)