xiongzhu 4 лет назад
Родитель
Сommit
b6987149ea

+ 12 - 0
src/main/java/com/izouma/nineth/annotations/Debounce.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Debounce {
+    String key();
+
+    long delay() default 200L;
+}

+ 71 - 0
src/main/java/com/izouma/nineth/aspect/DebounceAspect.java

@@ -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));
+
+    }
+
+}

+ 33 - 0
src/main/java/com/izouma/nineth/aspect/debounce/DebounceTask.java

@@ -0,0 +1,33 @@
+package com.izouma.nineth.aspect.debounce;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+@Slf4j
+public class DebounceTask implements Callable<Void> {
+    private final ProceedingJoinPoint  joinPoint;
+    private final Function<Void, Void> callback;
+
+    public DebounceTask(ProceedingJoinPoint joinPoint, Function<Void, Void> callback) {
+        this.joinPoint = joinPoint;
+        this.callback = callback;
+    }
+
+    @Override
+    public Void call() throws Exception {
+        try {
+            this.joinPoint.proceed();
+            if (this.callback != null) {
+                this.callback.apply(null);
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+
+        return null;
+
+    }
+}

+ 3 - 0
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.service;
 
 import com.alibaba.fastjson.JSON;
+import com.izouma.nineth.annotations.Debounce;
 import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
@@ -383,6 +384,7 @@ public class CollectionService {
         return sale;
     }
 
+    @Debounce(key = "#id", delay = 500)
     public void syncStock(Long id) {
         Integer stock = (Integer) redisTemplate.opsForValue().get("collectionStock::" + id);
         if (stock != null) {
@@ -392,6 +394,7 @@ public class CollectionService {
         }
     }
 
+    @Debounce(key = "#id", delay = 500)
     public void syncSale(Long id) {
         Integer sale = (Integer) redisTemplate.opsForValue().get("collectionSale::" + id);
         if (sale != null) {

+ 15 - 0
src/main/java/com/izouma/nineth/service/DebounceTestService.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.annotations.Debounce;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class DebounceTestService {
+
+    @Debounce(key = "#name", delay = 200)
+    public void testDebounce(String name, String data) {
+        log.info("running debounce method:{} {}", name, data);
+    }
+}

+ 19 - 0
src/test/java/com/izouma/nineth/CommonTest.java

@@ -61,6 +61,8 @@ import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
 import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.ForkJoinPool;
 import java.util.regex.Pattern;
 import java.util.stream.IntStream;
@@ -415,4 +417,21 @@ public class CommonTest {
             }
         }
     }
+
+    @Test
+    public void testnewSingleThreadScheduledExecutor() throws InterruptedException {
+        ExecutorService service = Executors.newFixedThreadPool(10);
+        final int[] c = {0};
+        for (int i = 0; i < 1000; i++) {
+            service.submit(new Runnable() {
+                @SneakyThrows
+                @Override
+                public void run() {
+                    Thread.sleep(300);
+                    System.out.println(++c[0]);
+                }
+            });
+        }
+        Thread.sleep(20000);
+    }
 }

+ 21 - 0
src/test/java/com/izouma/nineth/DebounceTest.java

@@ -0,0 +1,21 @@
+package com.izouma.nineth;
+
+import com.izouma.nineth.service.DebounceTestService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class DebounceTest extends ApplicationTests {
+    @Autowired
+    private DebounceTestService debounceTestService;
+
+    @Test
+    public void testDebounce() throws InterruptedException {
+        String[] names = new String[]{"name1"};
+        int c = 0;
+        for (int i = 0; i < 1000000000; i++) {
+            Thread.sleep(1);
+            debounceTestService.testDebounce(names[i % names.length], String.valueOf(++c));
+        }
+
+    }
+}