SpringBoot整合Redisson的AOP注解实现, 使用注解加锁, 简化操作
Redisson官网: https://redisson.org/
Redisson+AOP实现
1. SpringBoot导包
具体版本对应可以参考官网连接 https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
此处我使用的2.2.5的版本
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.7</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. 配置类编写
此处针对单机的redis进行配置, 比较简洁
RedisConfig.java
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);
return Redisson.create(config);
}
}
application.yaml
spring:
redis:
port: 6379
database: 0
host: localhost
3. 编写注解类
/**
* @author wangdi
* @date 21-7-8
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisDistributedLock {
/**
* 要锁哪几个位置的参数,默认不锁参数
* (如果锁参数, 需要指定参数的索引比如锁第一个参数和第二个参数则传{0, 1}
* 锁参数之后, 锁的key就会拼接此参数
*/
int[] lockIndex() default {-1};
/**
* 默认包名加方法名
*
* @return
*/
String key() default "";
/**
* 过期时间 单位:毫秒
* <pre>
* 过期时间一定是要长于业务的执行时间.
* </pre>
*/
long expire() default 30000;
/**
* 获取锁超时时间 单位:毫秒
* <pre>
* 结合业务,建议该时间不宜设置过长,特别在并发高的情况下.
* </pre>
*/
long timeout() default 3000;
/**
* 时间类型,默认毫秒
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
4. 编写切面类
/**
* @author wangdi
* @date 21-7-8
*/
@Configuration
@Aspect
@Slf4j
public class RedisLockAop {
@Resource
private RedissonClient redissonClient;
@Pointcut("@annotation(com.wd.annotation.RedisDistributedLock)")
public void myAdvice() {
}
@Around("myAdvice()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 获取注解
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
RedisDistributedLock annotation = signature.getMethod().getAnnotation(RedisDistributedLock.class);
// 生成key
StringBuilder keyBuilder = new StringBuilder(getKey(proceedingJoinPoint, annotation));
// 如果锁参数, 需要将参数拼接到key上
Object[] args = proceedingJoinPoint.getArgs();
int[] lockIndex = annotation.lockIndex();
if (lockIndex.length > 0 && lockIndex[0] >= 0) {
for (int index : lockIndex) {
if (index >= args.length || index < 0) {
throw new RuntimeException("参数索引lockIndex: " + index + " 异常");
}
keyBuilder.append(".").append(args[index].toString());
}
}
// 防止key值太长,用根据其生成的hash值做key
// String lockKey = DigestUtils.md5DigestAsHex(keyBuilder.toString().getBytes());
String key = keyBuilder.toString();
Boolean success = null;
RLock lock = redissonClient.getLock(key);
try {
//lock提供带timeout参数,timeout结束强制解锁,防止死锁
success = lock.tryLock(annotation.timeout(), annotation.expire(), annotation.timeUnit());
if (success) {
log.info(Thread.currentThread().getName() + " 加锁成功");
// 放行方法执行
return proceedingJoinPoint.proceed();
}
log.info(Thread.currentThread().getName() + " 加锁失败");
throw new RuntimeException("操作频繁, 稍后重试"); // 此处可以用return 返回错误 需要跟切的方法的返回值保持一致
} catch (Exception e) {
throw e;
} finally {
if (success) {
lock.unlock();
log.info(Thread.currentThread().getName() + " 解锁成功");
}
}
}
private String getKey(ProceedingJoinPoint proceedingJoinPoint, RedisDistributedLock annotation) {
if (!StringUtils.isEmpty(annotation.key())) {
return annotation.key();
}
return proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature()
.getName();
}
}
5. 测试
/**
* 手动加锁模拟 设置锁的时间为300秒 5分钟, 在5分钟之内若方法没有执行完成则自动解锁, 获取锁的等待时间为2秒
*/
@GetMapping("/test")
@RedisDistributedLock(timeUnit = TimeUnit.SECONDS, expire = 300, timeout = 2)
public String test(String id, String name) {
System.out.println("id = [" + id + "], name = [" + name + "]");
if (id.equals("1"))
throw new RuntimeException("出现异常");
try {
Thread.sleep(2 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "0";
}