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> debounceStore = new HashMap<>(); private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10); private Map 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 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)); } }