@@ -14,6 +14,36 @@ | |||||
<properties> | <properties> | ||||
<java.version>17</java.version> | <java.version>17</java.version> | ||||
<mybatis-plus.version>3.5.2</mybatis-plus.version> | |||||
</properties> | </properties> | ||||
<dependencies> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-aop</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>mysql</groupId> | |||||
<artifactId>mysql-connector-java</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.baomidou</groupId> | |||||
<artifactId>mybatis-plus-boot-starter</artifactId> | |||||
<version>${mybatis-plus.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.projectlombok</groupId> | |||||
<artifactId>lombok</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> | </project> |
@@ -0,0 +1,28 @@ | |||||
package com.xkcoding.distributed.lock.annotation; | |||||
import java.util.concurrent.TimeUnit; | |||||
/** | |||||
* <p> | |||||
* 分布式锁注解 | |||||
* </p> | |||||
* | |||||
* @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; | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 分布式锁切面 | |||||
* </p> | |||||
* | |||||
* @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); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 分布式锁接口 | |||||
* </p> | |||||
* | |||||
* @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"); | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package com.xkcoding.distributed.lock.api; | |||||
import java.util.concurrent.TimeUnit; | |||||
import java.util.function.Supplier; | |||||
/** | |||||
* <p> | |||||
* 分布式锁实现 | |||||
* </p> | |||||
* | |||||
* @author yangkai.shen | |||||
* @date 2022-09-02 21:11 | |||||
*/ | |||||
public interface DistributedLockService { | |||||
/** | |||||
* 锁 | |||||
* | |||||
* @param lockKey 锁的标识 | |||||
* @param lockTime 锁的时间 | |||||
* @param timeUnit 锁的时间单位 | |||||
* @param execute 执行逻辑 | |||||
* @param <T> 返回值类型 | |||||
* @return 返回值 | |||||
*/ | |||||
<T> T lock(String lockKey, long lockTime, TimeUnit timeUnit, Supplier<T> execute); | |||||
} |
@@ -0,0 +1,23 @@ | |||||
package com.xkcoding.distributed.lock.api; | |||||
import java.util.concurrent.TimeUnit; | |||||
/** | |||||
* <p> | |||||
* 锁客户端 | |||||
* </p> | |||||
* | |||||
* @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); | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 分布式锁实现 | |||||
* </p> | |||||
* | |||||
* @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> T lock(String lockKey, long timeout, TimeUnit timeUnit, Supplier<T> execute) { | |||||
DistributedLock lock = lockClient.getLock(lockKey, timeout, timeUnit); | |||||
lock.lock(); | |||||
try { | |||||
return execute.get(); | |||||
} catch (Throwable t) { | |||||
throw new RuntimeException(t); | |||||
} finally { | |||||
lock.unlock(); | |||||
} | |||||
} | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 无锁实现 | |||||
* </p> | |||||
* | |||||
* @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() { | |||||
} | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 获取一把虚拟锁 | |||||
* </p> | |||||
* | |||||
* @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); | |||||
} | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 自动装配类 | |||||
* </p> | |||||
* | |||||
* @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); | |||||
} | |||||
} |
@@ -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<Stock> { | |||||
} |
@@ -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; | |||||
} |
@@ -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<Stock>().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..."); | |||||
} | |||||
} |
@@ -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; | |||||
/** | |||||
* <p> | |||||
* 初始化数据 | |||||
* </p> | |||||
* | |||||
* @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("================================================"); | |||||
} | |||||
} |
@@ -0,0 +1,2 @@ | |||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | |||||
com.xkcoding.distributed.lock.autoconfigure.DistributedLockConfiguration |