|
16 | 16 | package com.reactify.annotations.cache; |
17 | 17 |
|
18 | 18 | import com.github.benmanes.caffeine.cache.Cache; |
19 | | -import java.util.Objects; |
| 19 | +import com.reactify.annotations.LocalCache; |
| 20 | +import com.reactify.util.DataUtil; |
20 | 21 | import java.util.Optional; |
21 | 22 | import org.aspectj.lang.ProceedingJoinPoint; |
22 | 23 | import org.aspectj.lang.annotation.Around; |
23 | 24 | import org.aspectj.lang.annotation.Aspect; |
24 | 25 | import org.aspectj.lang.annotation.Pointcut; |
| 26 | +import org.slf4j.Logger; |
| 27 | +import org.slf4j.LoggerFactory; |
25 | 28 | import org.springframework.cache.interceptor.SimpleKeyGenerator; |
26 | 29 | import org.springframework.context.annotation.Configuration; |
27 | 30 | import org.springframework.util.ClassUtils; |
|
31 | 34 |
|
32 | 35 | /** |
33 | 36 | * <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. |
39 | 38 | * </p> |
40 | 39 | * |
41 | 40 | * <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. |
52 | 43 | * </p> |
53 | 44 | * |
54 | 45 | * @author hoangtien2k3 |
|
58 | 49 | public class CacheAspect { |
59 | 50 |
|
60 | 51 | /** |
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 |
65 | 53 | */ |
| 54 | + private static final Logger log = LoggerFactory.getLogger(CacheAspect.class); |
| 55 | + |
66 | 56 | @Pointcut("@annotation(com.reactify.annotations.LocalCache)") |
67 | 57 | private void processAnnotation() {} |
68 | 58 |
|
69 | 59 | /** |
70 | 60 | * <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}. |
76 | 62 | * </p> |
77 | 63 | * |
78 | 64 | * @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 |
86 | 69 | */ |
87 | 70 | @Around("processAnnotation()") |
88 | 71 | public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { |
89 | 72 | Object[] args = joinPoint.getArgs(); |
90 | 73 | 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<?>")); |
101 | 91 | } |
| 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); |
102 | 98 | } |
| 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 | + }); |
103 | 108 | })); |
104 | 109 | } |
105 | 110 | } |
0 commit comments