Skip to content

Commit 25b0e93

Browse files
committed
reactify-core
1 parent 94bafdc commit 25b0e93

File tree

10 files changed

+395
-214
lines changed

10 files changed

+395
-214
lines changed

reactify-core/pom.xml

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
<!-- =========================================== -->
2828
<!-- The maven central for all the library -->
2929
<!-- =========================================== -->
30-
<groupId>com.ezbuy.platform</groupId>
30+
<groupId>io.github.hoangtien2k3</groupId>
3131
<artifactId>reactify-core</artifactId>
32-
<version>1.2.8</version>
32+
<version>1.2.9</version>
3333
<name>reactify-core</name>
3434
<description>Java library for developing reactive programming(reactor-core) backend systems in microservices</description>
3535
<url>https://github.com/hoangtien2k3/reactify-core</url>
@@ -74,20 +74,16 @@
7474
<java.version>21</java.version>
7575
<micrometer.tracing.version>1.4.3</micrometer.tracing.version>
7676
<micrometer.core.version>1.14.4</micrometer.core.version>
77+
<spotless.version>2.43.0</spotless.version>
7778
<minio.version>8.5.11</minio.version>
78-
<mapstruct.version>1.5.5.Final</mapstruct.version>
79-
<mapstruct.processor.version>1.5.5.Final</mapstruct.processor.version>
8079
<modelmapper.version>3.2.0</modelmapper.version>
81-
<lombok.version>1.18.32</lombok.version>
82-
<javax.annotation.version>1.3.2</javax.annotation.version>
83-
<jackson.databind.version>2.17.1</jackson.databind.version>
84-
<spotless.version>2.43.0</spotless.version>
8580
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
8681
<central-publishing-maven-plugin.version>0.6.0</central-publishing-maven-plugin.version>
8782
<nexus-staging-maven-plugin.version>1.7.0</nexus-staging-maven-plugin.version>
8883
<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
8984
<maven-javadoc-plugin.version>3.8.0</maven-javadoc-plugin.version>
9085
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
86+
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
9187
<reactor-extra.version>3.5.2</reactor-extra.version>
9288
<reflections.version>0.10.2</reflections.version>
9389
<jaxb-api.version>2.3.1</jaxb-api.version>
@@ -279,6 +275,18 @@
279275
<skip>true</skip>
280276
</configuration>
281277
</plugin>
278+
<plugin>
279+
<groupId>org.apache.maven.plugins</groupId>
280+
<artifactId>maven-jar-plugin</artifactId>
281+
<version>${maven-jar-plugin.version}</version>
282+
<configuration>
283+
<archive>
284+
<manifestEntries>
285+
<Automatic-Module-Name>com.reactify</Automatic-Module-Name>
286+
</manifestEntries>
287+
</archive>
288+
</configuration>
289+
</plugin>
282290
<plugin>
283291
<groupId>com.diffplug.spotless</groupId>
284292
<artifactId>spotless-maven-plugin</artifactId>

reactify-core/src/main/java/com/reactify/annotations/LocalCache.java

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,82 +21,99 @@
2121
import java.lang.annotation.Target;
2222

2323
/**
24-
* Annotation to indicate that a method's result should be cached locally. This
25-
* can improve performance by avoiding redundant computations or repeated data
26-
* retrieval from external sources.
24+
* Annotation for enabling local caching on method results to enhance
25+
* performance by reducing redundant computations and minimizing repeated data
26+
* retrievals from external sources such as databases or APIs.
2727
*
2828
* <p>
29-
* The caching behavior is defined by the properties of the annotation:
29+
* This annotation provides a flexible caching mechanism with configurable
30+
* properties:
3031
* <ul>
31-
* <li><strong>durationInMinute</strong>: The duration for which the cached
32-
* result is valid, specified in minutes.</li>
33-
* <li><strong>maxRecord</strong>: The maximum number of records that can be
34-
* stored in the cache.</li>
35-
* <li><strong>autoCache</strong>: A flag indicating whether caching should be
36-
* done automatically or manually.</li>
32+
* <li><strong>durationInMinute</strong>: Defines the lifespan of cached entries
33+
* in minutes.</li>
34+
* <li><strong>maxRecord</strong>: Specifies the maximum number of entries that
35+
* can be stored in the cache.</li>
36+
* <li><strong>autoCache</strong>: Determines whether caching should be
37+
* automatically applied when the method is invoked.</li>
3738
* </ul>
39+
* </p>
3840
*
39-
* <p>
40-
* This annotation should be applied to methods that return a result that can
41-
* benefit from caching.
41+
* <h3>Key Benefits:</h3>
42+
* <ul>
43+
* <li>Reduces redundant computations by caching method results.</li>
44+
* <li>Minimizes database or API calls, improving response times.</li>
45+
* <li>Optimizes resource usage in performance-critical applications.</li>
46+
* </ul>
4247
*
43-
* <h2>Usage Example:</h2>
48+
* <h3>Usage Example:</h3>
4449
*
4550
* <pre>
46-
* &#64;LocalCache(durationInMinute = 10, maxRecord = 500, autoCache = true)
47-
* public MyObject fetchData(String param) {
48-
* // Method implementation that retrieves data
51+
* {@code
52+
* @LocalCache(durationInMinute = 15, maxRecord = 200, autoCache = true)
53+
* public List<User> fetchActiveUsers() {
54+
* // Retrieves a list of active users from the database
55+
* }
4956
* }
5057
* </pre>
5158
*
52-
* <h2>Annotation Properties:</h2> <!-- Changed
53-
* <h3>to
54-
* <h2>-->
59+
* <h3>Annotation Properties:</h3>
5560
* <dl>
56-
* <dt>durationInMinute</dt>
57-
* <dd>The time period in minutes for which the cached data is valid. Default is
61+
* <dt><strong>durationInMinute</strong></dt>
62+
* <dd>Specifies how long (in minutes) the cache entry remains valid. Default is
5863
* 120 minutes.</dd>
5964
*
60-
* <dt>maxRecord</dt>
61-
* <dd>The maximum number of entries that can be cached. Default is 1000
62-
* entries.</dd>
65+
* <dt><strong>maxRecord</strong></dt>
66+
* <dd>Limits the number of records stored in the cache at any given time.
67+
* Default is 1000 entries.</dd>
6368
*
64-
* <dt>autoCache</dt>
65-
* <dd>If set to true, the method result will be automatically cached. Default
66-
* is false.</dd>
69+
* <dt><strong>autoCache</strong></dt>
70+
* <dd>If set to <code>true</code>, caching is applied automatically whenever
71+
* the method is executed. Default is <code>false</code>.</dd>
6772
* </dl>
6873
*
74+
* <h3>Best Practices:</h3>
75+
* <ul>
76+
* <li>Use on methods that return frequently accessed and computationally
77+
* expensive results.</li>
78+
* <li>Avoid applying to methods with frequently changing data to prevent stale
79+
* cache issues.</li>
80+
* <li>Adjust `durationInMinute` and `maxRecord` according to system load and
81+
* data update frequency.</li>
82+
* </ul>
83+
*
6984
* <p>
70-
* This annotation is intended for use in performance-sensitive applications
71-
* where reducing latency and resource consumption is critical.
85+
* This annotation is particularly useful in microservices and high-performance
86+
* applications where minimizing latency and optimizing resource utilization are
87+
* crucial.
7288
* </p>
73-
*
74-
* @author hoangtien2k3
7589
*/
7690
@Target(ElementType.METHOD)
7791
@Retention(RetentionPolicy.RUNTIME)
7892
public @interface LocalCache {
93+
7994
/**
80-
* Specifies the duration (in minutes) for which the cache entry is valid.
81-
* Default value is 120 minutes.
95+
* Defines the duration (in minutes) for which a cached entry remains valid.
96+
* After this time, the entry will expire and be removed from the cache.
8297
*
83-
* @return the duration in minutes
98+
* @return cache duration in minutes (default: 120)
8499
*/
85100
int durationInMinute() default 120;
86101

87102
/**
88-
* Specifies the maximum number of records that can be stored in the cache.
89-
* Default value is 1000 records.
103+
* Specifies the maximum number of records that can be stored in the cache. Once
104+
* this limit is reached, older entries may be evicted based on cache policies.
90105
*
91-
* @return the maximum number of records
106+
* @return maximum cache size (default: 1000)
92107
*/
93108
int maxRecord() default 1000;
94109

95110
/**
96-
* Indicates whether the caching should be enabled automatically. Default value
97-
* is false.
111+
* Indicates whether caching should be automatically applied when the method is
112+
* invoked. If enabled, the method execution result will be stored in the cache
113+
* for subsequent calls.
98114
*
99-
* @return true if auto-cache is enabled, false otherwise
115+
* @return <code>true</code> to enable automatic caching, <code>false</code>
116+
* otherwise (default: false)
100117
*/
101118
boolean autoCache() default false;
102119
}

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

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
package com.reactify.annotations.cache;
1717

1818
import com.github.benmanes.caffeine.cache.Cache;
19-
import java.util.Objects;
19+
import com.reactify.annotations.LocalCache;
20+
import com.reactify.util.DataUtil;
2021
import java.util.Optional;
2122
import org.aspectj.lang.ProceedingJoinPoint;
2223
import org.aspectj.lang.annotation.Around;
2324
import org.aspectj.lang.annotation.Aspect;
2425
import org.aspectj.lang.annotation.Pointcut;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
2528
import org.springframework.cache.interceptor.SimpleKeyGenerator;
2629
import org.springframework.context.annotation.Configuration;
2730
import org.springframework.util.ClassUtils;
@@ -31,24 +34,12 @@
3134

3235
/**
3336
* <p>
34-
* The {@code CacheAspect} class provides caching functionality for methods
35-
* annotated with {@link com.reactify.annotations.LocalCache}. This aspect
36-
* intercepts method calls, checks the cache for existing results, and returns
37-
* cached results if available. If no cached result is found, the method is
38-
* executed and the result is stored in the cache for future calls.
37+
* Aspect for handling caching via @LocalCache annotation.
3938
* </p>
4039
*
4140
* <p>
42-
* This class uses Spring AOP (Aspect-Oriented Programming) features to
43-
* implement caching logic around method executions, providing a way to enhance
44-
* performance by avoiding redundant computations or data retrievals.
45-
* </p>
46-
*
47-
* <p>
48-
* The class is annotated with {@link org.aspectj.lang.annotation.Aspect} and
49-
* {@link org.springframework.context.annotation.Configuration}, making it a
50-
* Spring managed bean that can intercept method calls. The logging is managed
51-
* using the Lombok {@link lombok.extern.slf4j.Slf4j} annotation.
41+
* This aspect intercepts methods annotated with {@link LocalCache} and provides
42+
* caching functionality using Caffeine.
5243
* </p>
5344
*
5445
* @author hoangtien2k3
@@ -58,48 +49,62 @@
5849
public class CacheAspect {
5950

6051
/**
61-
* <p>
62-
* Pointcut that matches methods annotated with
63-
* {@link com.reactify.annotations.LocalCache}.
64-
* </p>
52+
* A static logger instance for logging messages
6553
*/
54+
private static final Logger log = LoggerFactory.getLogger(CacheAspect.class);
55+
6656
@Pointcut("@annotation(com.reactify.annotations.LocalCache)")
6757
private void processAnnotation() {}
6858

6959
/**
7060
* <p>
71-
* Around advice that intercepts method calls annotated with
72-
* {@link com.reactify.annotations.LocalCache}. This method checks for a cached
73-
* result using the generated key based on the method arguments. If a cached
74-
* result is found, it is returned; otherwise, the method is executed, and the
75-
* result is stored in the cache.
61+
* Handles caching logic for methods annotated with {@link LocalCache}.
7662
* </p>
7763
*
7864
* @param joinPoint
79-
* a {@link org.aspectj.lang.ProceedingJoinPoint} object representing
80-
* the method execution context.
81-
* @return an {@link java.lang.Object} that is the result of the method
82-
* execution or the cached result.
83-
* @throws java.lang.Throwable
84-
* if any exception occurs during method execution or while
85-
* accessing the cache.
65+
* the intercepted method call
66+
* @return cached or computed result
67+
* @throws Throwable
68+
* if an exception occurs during method execution
8669
*/
8770
@Around("processAnnotation()")
8871
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
8972
Object[] args = joinPoint.getArgs();
9073
Object key = SimpleKeyGenerator.generateKey(args);
91-
String name = ClassUtils.getUserClass(joinPoint.getTarget().getClass()).getSimpleName() + "."
92-
+ joinPoint.getSignature().getName();
93-
Cache<Object, Object> cache = CacheStore.getCache(name);
94-
return CacheMono.lookup(k -> Mono.justOrEmpty(cache.getIfPresent(key)).map(Signal::next), key)
95-
.onCacheMissResume((Mono<Object>) joinPoint.proceed(args))
96-
.andWriteWith((k, sig) -> Mono.fromRunnable(() -> {
97-
if (sig != null && sig.get() != null) {
98-
if (!(sig.get() instanceof Optional
99-
&& ((Optional<?>) Objects.requireNonNull(sig.get())).isEmpty())) {
100-
cache.put(k, sig.get());
74+
String nameCache = ClassUtils.getUserClass(joinPoint.getTarget().getClass())
75+
.getSimpleName() + "." + joinPoint.getSignature().getName();
76+
Cache<Object, Object> cache = CacheStore.getCache(nameCache);
77+
log.debug("Checking cache for method: {} with key: {}", nameCache, key);
78+
return CacheMono.lookup(
79+
k -> Mono.justOrEmpty(!DataUtil.isNullOrEmpty(cache) ? cache.getIfPresent(key) : Mono.empty())
80+
.map(Signal::next),
81+
key)
82+
.onCacheMissResume(Mono.defer(() -> {
83+
try {
84+
Object result = joinPoint.proceed(args);
85+
if (!(result instanceof Mono<?>)) {
86+
log.warn(
87+
"Method {} must return a Mono<?> but got: {}",
88+
nameCache,
89+
result.getClass().getSimpleName());
90+
return Mono.error(new IllegalStateException("Method must return Mono<?>"));
10191
}
92+
@SuppressWarnings("unchecked")
93+
var resultCast = (Mono<Object>) result;
94+
return resultCast;
95+
} catch (Throwable ex) {
96+
log.error("Execution error in {} - {}", nameCache, ex.getMessage(), ex);
97+
return Mono.error(ex);
10298
}
99+
}))
100+
.andWriteWith((k, sig) -> Mono.fromRunnable(() -> {
101+
Optional.ofNullable(sig)
102+
.map(Signal::get)
103+
.filter(value -> !(value instanceof Optional && ((Optional<?>) value).isEmpty()))
104+
.ifPresent(value -> {
105+
cache.put(k, value);
106+
log.debug("Cached value for key: {} in method: {}", k, nameCache);
107+
});
103108
}));
104109
}
105110
}

0 commit comments

Comments
 (0)