@@ -14,6 +14,36 @@ | |||
<properties> | |||
<java.version>17</java.version> | |||
<mybatis-plus.version>3.5.2</mybatis-plus.version> | |||
</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> |
@@ -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 |