在Spring Boot应用中,结合Redisson和自定义注解可以实现分布式锁的功能,从而在多线程或微服务环境下同步访问共享资源。下面是一个使用Redisson和Spring Boot 结合的案例,用于实现分布式锁。

1. 添加依赖

首先,需要在项目的pom.xml文件中添加Redisson Spring Boot Starter依赖。

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.1</version>
</dependency>

2. 配置Redisson

application.ymlapplication.properties中配置Redisson客户端。

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: your-redis-password
  redisson:
    file: classpath:redisson.yml

redisson.yml配置文件内容:

redisson:
  threads: 0
  codec: org.redisson.codec.JsonJacksonCodec
  transportMode: NIO
  address: "redis://127.0.0.1:6379"
  password: your-redis-password

3. 创建自定义注解

定义一个@RedisLock注解,用于标记需要加锁的方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    String value() default "";
    long waitTime() default 10;
    long leaseTime() default 10;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

4. 创建注解处理器

创建一个RedisLockAnnotationProcessor类,用于处理@RedisLock注解。

@Component
public class RedisLockAnnotationProcessor implements MethodInterceptor {

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        if (redisLock != null) {
            String lockKey = "lock:" + redisLock.value().isEmpty() ? method.getName() : redisLock.value();
            RLock lock = redissonClient.getLock(lockKey);
            try {
                if (!lock.tryLock(redisLock.waitTime(), redisLock.leaseTime(), redisLock.timeUnit())) {
                    throw new ServiceException("锁被占用,请稍后提交!");
                }
                return invocation.proceed();
            } finally {
                lock.unlock();
            }
        }
        return invocation.proceed();
    }
}

RedisLockAnnotationProcessor 是一个运行时注解处理器,它实现了 Spring AOP 的 MethodInterceptor 接口。这个处理器将在方法执行前后拦截那些标记有 @RedisLock 注解的方法,并执行与分布式锁相关的逻辑。

下面是对这个注解处理器的详细解析:

  1. @Component:这个注解表明 RedisLockAnnotationProcessor 类是一个 Spring 组件,Spring 容器应该创建它的实例并管理它的生命周期。
  2. public class RedisLockAnnotationProcessor implements MethodInterceptor:这个类实现了 MethodInterceptor 接口,这意味着它将拦截方法调用并可以定义方法执行前后的行为。
  3. @Autowired:这个注解自动注入了一个 RedissonClient 实例,用于与 Redisson 服务器进行交互,如获取分布式锁。
  4. public Object invoke(MethodInvocation invocation) throws Throwable:这是 MethodInterceptor 接口的核心方法,它在目标方法被调用之前和之后执行。方法的返回值是 Object 类型,表示它可以返回任何类型的值。
  5. Method method = invocation.getMethod();:获取当前被拦截的方法的信息。
  6. RedisLock redisLock = method.getAnnotation(RedisLock.class);:检查当前方法是否使用了 @RedisLock 注解。
  7. if (redisLock != null) { ... }:如果方法上有 @RedisLock 注解,执行相关的逻辑。
  8. String lockKey = "lock:" + redisLock.value().isEmpty() ? method.getName() : redisLock.value();:构造锁的键名,如果 @RedisLock 注解的 value 属性为空,则使用方法名作为锁的键。
  9. RLock lock = redissonClient.getLock(lockKey);:通过 Redisson 客户端获取一个分布式锁实例。
  10. if (!lock.tryLock(redisLock.waitTime(), redisLock.leaseTime(), redisLock.timeUnit())) { ... }:尝试获取锁,如果无法获取锁,则抛出异常。
  11. return invocation.proceed();:如果成功获取锁,继续执行原方法。
  12. finally { lock.unlock(); }:无论方法执行是否成功,最终释放锁。

这个注解处理器的逻辑确保了在执行被 @RedisLock 注解的方法时,会先尝试获取一个分布式锁,以防止并发访问造成的问题。如果获取锁失败,则会抛出异常,否则执行原方法并在执行结束后释放锁。这种方式在分布式系统中非常有用,特别是在需要确保操作的原子性和一致性的场景中。

5. 配置注解驱动

在Spring Boot应用中启用注解驱动。

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class RedisLockConfig {

    @Bean
    public RedisLockAnnotationProcessor redisLockAnnotationProcessor() {
        return new RedisLockAnnotationProcessor();
    }

    @Bean
    public Advisor advisor(RedisLockAnnotationProcessor processor) {
        Pointcut pointcut = AspectJExpressionPointcut.parseExpression("execution(* *(..))");
        return new DefaultAdvisor(pointcut, processor);
    }
}

这段代码是一个Spring Boot配置类,用于配置AspectJ切面以支持自定义注解的处理。下面是对代码的逐行解析:

  1. @Configuration:这是一个Spring注解,用于定义一个配置类。配置类中可以包含Bean的定义,这些Bean将被Spring容器管理。
  2. @EnableAspectJAutoProxy(exposeProxy = true):这是Spring的切面注解,用于启用AspectJ自动代理。这意味着Spring会自动为匹配的Bean创建代理,以便可以在运行时应用切面逻辑。exposeProxy属性设置为true表示将代理对象暴露给被代理的Bean,这允许被代理的Bean访问自己的代理(例如,用于日志记录或性能监控)。
  3. public class RedisLockConfig:这是配置类的名字。
  4. @Bean:这是Spring的注解,用于定义一个Bean。在Spring容器启动时,会调用带有@Bean注解的方法,并将其返回值注册为Spring容器中的Bean。
  5. public RedisLockAnnotationProcessor redisLockAnnotationProcessor():这是一个Bean的定义方法。它返回一个RedisLockAnnotationProcessor类型的实例,这个类可能是一个自定义的注解处理器,用于处理特定的注解(例如,用于处理分布式锁的注解)。
  6. return new RedisLockAnnotationProcessor();:这行代码创建并返回了一个RedisLockAnnotationProcessor实例。
  7. @Bean:这是另一个Bean的定义方法。
  8. public Advisor advisor(RedisLockAnnotationProcessor processor):这个方法定义了一个Advisor Bean,它由两个主要部分组成:Pointcut和Advice(在这里是通过RedisLockAnnotationProcessor实例提供的)。
  9. Pointcut pointcut = AspectJExpressionPointcut.parseExpression("execution(* *(..))");:这行代码创建了一个Pointcut实例,它定义了切面应该应用的位置。这里使用的是AspectJ表达式,表示对所有方法的执行进行匹配。
  10. return new DefaultAdvisor(pointcut, processor);:这行代码创建并返回了一个DefaultAdvisor实例,它将前面定义的Pointcut和RedisLockAnnotationProcessor实例作为参数。DefaultAdvisor是一个Advisor的具体实现,它告诉Spring AOP在哪些JoinPoint(连接点,即方法的执行)上应用哪个Advice。

这段代码定义了一个Spring配置类RedisLockConfig,它注册了两个Bean:一个RedisLockAnnotationProcessor用于处理特定的注解,和一个Advisor用于指定切面逻辑应该应用的位置。通过这种方式,Spring Boot应用可以在运行时自动处理带有特定注解的方法,例如实现分布式锁的功能。

6. 使用注解

在需要加锁的方法上使用@RedisLock注解。

@RestController
public class MyController {

    @RedisLock("user:lock", waitTime = 5, leaseTime = 10, timeUnit = TimeUnit.SECONDS)
    @GetMapping("/user")
    public String getUser() {
        // 业务逻辑处理
        return "User Info";
    }
}

总结

通过上述步骤,我们成功地将Redisson与Spring Boot结合起来,实现了一个简单的分布式锁功能。开发者只需在方法上添加@RedisLock注解,并设置相应的参数,就可以控制并发访问,避免数据不一致的问题。这种机制在处理共享资源或执行幂等性操作时非常有用。同时,使用注解的方式也让代码更加简洁和易于维护。