|
@@ -0,0 +1,71 @@
|
|
|
|
|
+package com.izouma.nineth.aspect;
|
|
|
|
|
+
|
|
|
|
|
+import com.izouma.nineth.annotations.Debounce;
|
|
|
|
|
+import com.izouma.nineth.aspect.debounce.DebounceTask;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.aspectj.lang.ProceedingJoinPoint;
|
|
|
|
|
+import org.aspectj.lang.annotation.Around;
|
|
|
|
|
+import org.aspectj.lang.annotation.Aspect;
|
|
|
|
|
+import org.aspectj.lang.annotation.Pointcut;
|
|
|
|
|
+import org.aspectj.lang.reflect.MethodSignature;
|
|
|
|
|
+import org.springframework.core.DefaultParameterNameDiscoverer;
|
|
|
|
|
+import org.springframework.expression.EvaluationContext;
|
|
|
|
|
+import org.springframework.expression.ExpressionParser;
|
|
|
|
|
+import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
|
|
|
+import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Method;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Optional;
|
|
|
|
|
+import java.util.concurrent.Executors;
|
|
|
|
|
+import java.util.concurrent.Future;
|
|
|
|
|
+import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+@Aspect
|
|
|
|
|
+@Component
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+public class DebounceAspect {
|
|
|
|
|
+
|
|
|
|
|
+ private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
|
|
|
|
|
+ private HashMap<String, Future<Void>> debounceStore = new HashMap<>();
|
|
|
|
|
+ private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
|
|
|
|
|
+ private Map<String, Long> debounceCounter = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ @Pointcut("@annotation(com.izouma.nineth.annotations.Debounce)")
|
|
|
|
|
+ public void debouncePointCut() {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Around(value = "debouncePointCut() && @annotation(debounce)")
|
|
|
|
|
+ public synchronized void debounce(ProceedingJoinPoint joinPoint, Debounce debounce) {
|
|
|
|
|
+ ExpressionParser parser = new SpelExpressionParser();
|
|
|
|
|
+ EvaluationContext context = new StandardEvaluationContext(joinPoint.getSignature());
|
|
|
|
|
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
|
|
|
|
+ Method method = methodSignature.getMethod();
|
|
|
|
|
+ String[] paramNames = nameDiscoverer.getParameterNames(method);
|
|
|
|
|
+ Object[] args = joinPoint.getArgs();
|
|
|
|
|
+ for (int i = 0; i < args.length; i++) {
|
|
|
|
|
+ context.setVariable(paramNames[i], args[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ String key = Optional.ofNullable(parser.parseExpression(debounce.key()).getValue(context)).map(Object::toString)
|
|
|
|
|
+ .orElse("default");
|
|
|
|
|
+
|
|
|
|
|
+ Future<Void> future = debounceStore.get(key);
|
|
|
|
|
+ long lastRun = debounceCounter.getOrDefault(key, 0L);
|
|
|
|
|
+ if (future != null && !future.isDone()) {
|
|
|
|
|
+ if (System.currentTimeMillis() - lastRun > debounce.delay()) {
|
|
|
|
|
+ debounceCounter.put(key, System.currentTimeMillis());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ future.cancel(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ debounceStore.put(key, executorService.schedule(new DebounceTask(joinPoint, (Void) -> {
|
|
|
|
|
+ debounceCounter.put(key, System.currentTimeMillis());
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }), debounce.delay(), TimeUnit.MILLISECONDS));
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|