diff --git a/demo-distributed-lock/demo-distributed-lock-api/pom.xml b/demo-distributed-lock/demo-distributed-lock-api/pom.xml index 7ff9bb3..a65623b 100644 --- a/demo-distributed-lock/demo-distributed-lock-api/pom.xml +++ b/demo-distributed-lock/demo-distributed-lock-api/pom.xml @@ -14,6 +14,36 @@ 17 + 3.5.2 + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + mysql + mysql-connector-java + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + org.projectlombok + lombok + true + + + diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/annotation/DLock.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/annotation/DLock.java new file mode 100644 index 0000000..2d85222 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/annotation/DLock.java @@ -0,0 +1,28 @@ +package com.xkcoding.distributed.lock.annotation; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 分布式锁注解 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 15:47 + */ +public @interface DLock { + /** + * @return 锁的标识,支持 spel 表达式 + */ + String lockKey() default "lock"; + + /** + * @return 锁的时间 + */ + long lockTime() default 3000; + + /** + * @return 锁的时间单位 + */ + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/aop/DistributedLockAspect.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/aop/DistributedLockAspect.java new file mode 100644 index 0000000..88af59f --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/aop/DistributedLockAspect.java @@ -0,0 +1,68 @@ +package com.xkcoding.distributed.lock.aop; + +import com.xkcoding.distributed.lock.annotation.DLock; +import com.xkcoding.distributed.lock.api.DistributedLockService; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +/** + *

+ * 分布式锁切面 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:02 + */ +@Aspect +@RequiredArgsConstructor +public class DistributedLockAspect { + private final DistributedLockService distributedLockService; + + @Around("@annotation(lock)") + public Object around(ProceedingJoinPoint pjp, DLock lock) throws Throwable { + Method method = ((MethodSignature) pjp.getSignature()).getMethod(); + Object[] args = pjp.getArgs(); + String lockKey = lock.lockKey(); + lockKey = parseExpression(lockKey, method, args); + + long timeout = lock.lockTime(); + TimeUnit timeUnit = lock.timeUnit(); + return distributedLockService.lock(lockKey, timeout, timeUnit, () -> { + try { + return pjp.proceed(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } + + /** + * 解析 spel 表达式 + */ + private String parseExpression(String expression, Method method, Object[] args) { + LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + String[] params = nameDiscoverer.getParameterNames(method); + + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + for (int i = 0; i < params.length; i++) { + context.setVariable(params[i], args[i]); + } + try { + return parser.parseExpression(expression).getValue(context, String.class); + } catch (SpelEvaluationException e) { + throw new RuntimeException("spel 表达式解析错误", e); + } + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLock.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLock.java new file mode 100644 index 0000000..187598d --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLock.java @@ -0,0 +1,44 @@ +package com.xkcoding.distributed.lock.api; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + *

+ * 分布式锁接口 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:30 + */ +public abstract class DistributedLock implements Lock { + /** + * 锁的标识 + */ + private final String lockKey; + /** + * 锁的时间 + */ + private final long lockTime; + /** + * 锁的时间单位 + */ + private final TimeUnit timeUnit; + + protected DistributedLock(String lockKey, long lockTime, TimeUnit timeUnit) { + this.lockKey = lockKey; + this.lockTime = lockTime; + this.timeUnit = timeUnit; + } + + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException("DistributedLock`s lockInterruptibly method is unsupported"); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException("DistributedLock`s newCondition method is unsupported"); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLockService.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLockService.java new file mode 100644 index 0000000..7b8c432 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/DistributedLockService.java @@ -0,0 +1,27 @@ +package com.xkcoding.distributed.lock.api; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + *

+ * 分布式锁实现 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:11 + */ +public interface DistributedLockService { + + /** + * 锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @param execute 执行逻辑 + * @param 返回值类型 + * @return 返回值 + */ + T lock(String lockKey, long lockTime, TimeUnit timeUnit, Supplier execute); +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/LockClient.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/LockClient.java new file mode 100644 index 0000000..e6812c5 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/LockClient.java @@ -0,0 +1,23 @@ +package com.xkcoding.distributed.lock.api; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 锁客户端 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:48 + */ +public interface LockClient { + /** + * 获取一把锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @return 锁 + */ + DistributedLock getLock(String lockKey, long lockTime, TimeUnit timeUnit); +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DistributedLockServiceImpl.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DistributedLockServiceImpl.java new file mode 100644 index 0000000..8a54723 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DistributedLockServiceImpl.java @@ -0,0 +1,47 @@ +package com.xkcoding.distributed.lock.api.impl; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import com.xkcoding.distributed.lock.api.DistributedLockService; +import com.xkcoding.distributed.lock.api.LockClient; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + *

+ * 分布式锁实现 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:41 + */ +@Slf4j +@AllArgsConstructor +public class DistributedLockServiceImpl implements DistributedLockService { + private final LockClient lockClient; + + /** + * 锁 + * + * @param lockKey 锁 + * @param timeout 超时时间 + * @param timeUnit 超时单位 + * @param execute 执行逻辑 + * @return 返回值 + */ + @Override + public T lock(String lockKey, long timeout, TimeUnit timeUnit, Supplier execute) { + DistributedLock lock = lockClient.getLock(lockKey, timeout, timeUnit); + lock.lock(); + + try { + return execute.get(); + } catch (Throwable t) { + throw new RuntimeException(t); + } finally { + lock.unlock(); + } + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLock.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLock.java new file mode 100644 index 0000000..90e523b --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLock.java @@ -0,0 +1,41 @@ +package com.xkcoding.distributed.lock.api.impl; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 无锁实现 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:32 + */ +public class DummyDistributedLock extends DistributedLock { + + protected DummyDistributedLock(String lockKey, long lockTime, TimeUnit timeUnit) { + super(lockKey, lockTime, timeUnit); + } + + @Override + public void lock() { + // Do nothing. + } + + @Override + public boolean tryLock() { + return true; + } + + @Override + public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException { + return true; + } + + @Override + public void unlock() { + + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLockClient.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLockClient.java new file mode 100644 index 0000000..30abc11 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/api/impl/DummyDistributedLockClient.java @@ -0,0 +1,29 @@ +package com.xkcoding.distributed.lock.api.impl; + +import com.xkcoding.distributed.lock.api.DistributedLock; +import com.xkcoding.distributed.lock.api.LockClient; + +import java.util.concurrent.TimeUnit; + +/** + *

+ * 获取一把虚拟锁 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:53 + */ +public class DummyDistributedLockClient implements LockClient { + /** + * 获取一把锁 + * + * @param lockKey 锁的标识 + * @param lockTime 锁的时间 + * @param timeUnit 锁的时间单位 + * @return 锁 + */ + @Override + public DistributedLock getLock(String lockKey, long lockTime, TimeUnit timeUnit) { + return new DummyDistributedLock(lockKey, lockTime, timeUnit); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/autoconfigure/DistributedLockConfiguration.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/autoconfigure/DistributedLockConfiguration.java new file mode 100644 index 0000000..78a99dd --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/autoconfigure/DistributedLockConfiguration.java @@ -0,0 +1,31 @@ +package com.xkcoding.distributed.lock.autoconfigure; + +import com.xkcoding.distributed.lock.api.DistributedLockService; +import com.xkcoding.distributed.lock.api.LockClient; +import com.xkcoding.distributed.lock.api.impl.DistributedLockServiceImpl; +import com.xkcoding.distributed.lock.api.impl.DummyDistributedLockClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

+ * 自动装配类 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 21:57 + */ +@Configuration(proxyBeanMethods = false) +public class DistributedLockConfiguration { + @Bean + @ConditionalOnMissingBean + public LockClient lockClient() { + return new DummyDistributedLockClient(); + } + + @Bean + public DistributedLockService distributedLockService(LockClient lockClient) { + return new DistributedLockServiceImpl(lockClient); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/mapper/StockMapper.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/mapper/StockMapper.java new file mode 100644 index 0000000..e6eee49 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/mapper/StockMapper.java @@ -0,0 +1,15 @@ +package com.xkcoding.distributed.lock.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.xkcoding.distributed.lock.model.Stock; +import org.apache.ibatis.annotations.Mapper; + +/** + * 货物 Mapper + * + * @author yangkai.shen + * @date 2022-09-02 14:09 + */ +@Mapper +public interface StockMapper extends BaseMapper { +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/model/Stock.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/model/Stock.java new file mode 100644 index 0000000..55c88dd --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/model/Stock.java @@ -0,0 +1,29 @@ +package com.xkcoding.distributed.lock.model; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 货物 + * + * @author yangkai.shen + * @date 2022-09-02 14:07 + */ +@Data +@TableName("db_stock") +public class Stock { + /** + * 主键 + */ + @TableId + private Long id; + /** + * 货物名称 + */ + private String name; + /** + * 货物总数 + */ + private Long count; +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/service/StockService.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/service/StockService.java new file mode 100644 index 0000000..cc09e31 --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/service/StockService.java @@ -0,0 +1,58 @@ +package com.xkcoding.distributed.lock.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xkcoding.distributed.lock.annotation.DLock; +import com.xkcoding.distributed.lock.mapper.StockMapper; +import com.xkcoding.distributed.lock.model.Stock; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +/** + * 模拟仓库 Service + * + * @author yangkai.shen + * @date 2022-09-02 14:05 + */ +@Slf4j +@Service +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class StockService { + private final StockMapper stockMapper; + + /** + * 减货物 + */ + @DLock(lockKey = "lock_stock_${stockId}", lockTime = 3000, timeUnit = TimeUnit.MICROSECONDS) + public void reduceStock(Long stockId) { + // 先查询库存是否充足 + Stock stock = this.stockMapper.selectById(stockId); + + // 再减库存 + if (stock != null && stock.getCount() > 0) { + stock.setCount(stock.getCount() - 1); + this.stockMapper.updateById(stock); + } + } + + /** + * 重置货物 + */ + public void resetStock() { + log.info("start to init stock data..."); + + stockMapper.delete(new LambdaQueryWrapper().gt(Stock::getId, 0)); + + Stock mockStock = new Stock(); + mockStock.setId(1L); + mockStock.setName("测试商品"); + mockStock.setCount(5000L); + + stockMapper.insert(mockStock); + + log.info("stock data has been initialized..."); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/task/InitStockTask.java b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/task/InitStockTask.java new file mode 100644 index 0000000..7772e8a --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/java/com/xkcoding/distributed/lock/task/InitStockTask.java @@ -0,0 +1,31 @@ +package com.xkcoding.distributed.lock.task; + +import com.xkcoding.distributed.lock.service.StockService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +/** + *

+ * 初始化数据 + *

+ * + * @author yangkai.shen + * @date 2022-09-02 15:35 + */ +@Slf4j +@Component +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class InitStockTask implements ApplicationRunner { + private final StockService stockService; + + @Override + public void run(ApplicationArguments args) throws Exception { + log.info("================================================"); + stockService.resetStock(); + log.info("================================================"); + } +} diff --git a/demo-distributed-lock/demo-distributed-lock-api/src/main/resources/META-INF/spring.factories b/demo-distributed-lock/demo-distributed-lock-api/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..ad1e9df --- /dev/null +++ b/demo-distributed-lock/demo-distributed-lock-api/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.xkcoding.distributed.lock.autoconfigure.DistributedLockConfiguration