下面将详细介绍如何在 Spring Boot 里借助注解实现分布式锁,以login_lock:
作为锁的 key 前缀,使用请求参数里的phone
值作为 key,等待时间设为 0 秒,锁的持续时间为 10 秒。我们会使用 Redis 来实现分布式锁,同时借助 Spring AOP 与自定义注解达成基于注解的锁机制。
1. 添加依赖
在pom.xml
文件中添加必要的依赖,包括 Spring Boot Redis 和 Spring Boot AOP:
xml
<dependencies><!-- Spring Boot Redis 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Boot AOP 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
</dependencies>
2. 配置 Redis
在application.properties
或者application.yml
中配置 Redis 连接信息,以application.yml
为例:
yaml
spring:redis:host: localhostport: 6379# 若 Redis 有密码,需添加此项# password: yourpassword
3. 定义分布式锁注解
创建自定义注解DistributedLock
,用于标记需要加锁的方法:
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {String keyPrefix() default "login_lock:";String keyField() default "phone";long waitTime() default 0;long leaseTime() default 10;
}
4. 实现分布式锁切面
创建切面类DistributedLockAspect
,处理加锁和解锁逻辑:
java
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Collections;
import java.util.concurrent.TimeUnit;@Aspect
@Component
public class DistributedLockAspect {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private static final RedisScript<Long> UNLOCK_SCRIPT;static {StringBuilder script = new StringBuilder();script.append("if redis.call('get', KEYS[1]) == ARGV[1] then");script.append(" return redis.call('del', KEYS[1])");script.append("else");script.append(" return 0");script.append("end");UNLOCK_SCRIPT = new DefaultRedisScript<>(script.toString(), Long.class);}@Around("@annotation(com.example.demo.DistributedLock)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();DistributedLock distributedLock = signature.getMethod().getAnnotation(DistributedLock.class);String keyPrefix = distributedLock.keyPrefix();String keyField = distributedLock.keyField();long waitTime = distributedLock.waitTime();long leaseTime = distributedLock.leaseTime();Object[] args = joinPoint.getArgs();String keyValue = null;for (Object arg : args) {try {Field field = arg.getClass().getDeclaredField(keyField);field.setAccessible(true);keyValue = String.valueOf(field.get(arg));break;} catch (NoSuchFieldException | IllegalAccessException e) {// 忽略异常,继续尝试下一个参数}}if (keyValue == null) {throw new IllegalArgumentException("Could not find the key field in the method arguments.");}String lockKey = keyPrefix + keyValue;String requestId = java.util.UUID.randomUUID().toString();long startTime = System.currentTimeMillis();boolean locked = false;do {locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, leaseTime, TimeUnit.SECONDS);if (locked) {break;}// 检查是否超过等待时间if (System.currentTimeMillis() - startTime >= waitTime * 1000) {break;}// 短暂休眠后重试Thread.sleep(100);} while (true);if (!locked) {throw new RuntimeException("Failed to acquire the lock after waiting.");}try {return joinPoint.proceed();} finally {// 使用 Lua 脚本释放锁,保证原子性redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKey), requestId);}}
}
5. 使用分布式锁注解
在需要加锁的方法上添加DistributedLock
注解:
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class LoginController {@PostMapping("/login")@DistributedLockpublic String login(@RequestBody User user) {// 模拟业务逻辑try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return "Login success";}
}class User {private String phone;public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}
}
代码解释
DistributedLock
注解:用于标记需要加锁的方法,可通过keyPrefix
、keyField
、waitTime
和leaseTime
属性配置锁的相关信息。DistributedLockAspect
切面类:- 运用
@Around
注解拦截所有标记了DistributedLock
注解的方法。 - 从方法参数里提取
phone
值,组合成 Redis 锁的 key。 - 借助
redisTemplate.opsForValue().setIfAbsent
方法尝试获取锁,若获取失败则抛出异常。 - 使用 Lua 脚本释放锁,确保操作的原子性,防止误删其他线程的锁。
- 运用
LoginController
控制器:在login
方法上添加DistributedLock
注解,保证同一手机号在同一时间只有一个请求能进入该方法。
通过以上步骤,你就能在 Spring Boot 项目中使用注解实现分布式锁。