使用Aspect+自定义注解实现redis锁,防止重复触发

使用Aspect+自定义注解实现redis锁,防止重复触发

自定义注解类

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
    String value() default "default_lock:";
}

切面类

@Aspect
//@Order(1)
@Component
public class RedisLockAspect {
//    @Around("@annotation(com.common.aspect.RedisLock)")
    @Around("@annotation(redisLock)")
    public Object lock(ProceedingJoinPoint point, RedisLock redisLock) {
        //获取参数
        Object[] args = point.getArgs();
        Object result = null;
        int length = args.length;
        if (args != null && length > 0) {
            try {
                //默认锁第一个参数,userid
                String userid = String.valueOf(args[0]);
                //锁的key
                String lockName = redisLock.value()+userid;
                Boolean aBoolean = RedisHelper.setNX(lockName, "1");
                if (!aBoolean) {
                    result = Utils.toJSON("请求过于频繁,请稍后再试", null, "1");;
                } else {
                    //设置缓存时间,这个地方报错可能会产生死锁 设置缓存时间 60s(秒)
                    RedisHelper.expire(lockName, 60);
                    //执行方法
                    result = point.proceed(args);
                    //方法执行完,删除key
                    RedisHelper.del(lockName);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        return result;
    } 
}

RedisHelper是操作redis的工具类,网上有很多,不上传了。
切面类中
1.@Around();环绕通知, 包围一个join point(连接点)的通知,如方法调用。这是最强大的一种通知 类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
2.point.proceed(args); 执行目标方法(标注注解的方法)。
3. RedisHelper.setNX(lockName, “1”); 这是redis的工具类,就不发了,setNX命令就是 set not exist,
如果redis缓存中存在该key值,不更新,返回false,不存在就存入redis缓存中。返回true。
RedisHelper.expire(lockName, 60); 设置缓存时间 60s
4.例子中的redis锁的key是@RedisLock 中的参数和方法的第一个参数组成的。

切入点表达式:
@annotation(com.common.aspect.RedisLock) 任何一个执行的方法有一个 @RedisLock 注解的连接点(在Spring AOP中只是方法执行)(标注@RedisLock注解的方法)
@annotation(redisLock) 如果在@RedisLock 注解中使用参数名字代替参数类型,那么当通知执行的时候,对应的参数值会被传入。(@RedisLock注解本身可以作为切面方法中的参数,方便获取注解中的参数值)

使用实例:

@Service
public class TestService {
   	 @RedisLock(“test”)
    public Object test1(String userid) {
        return1111111111;
    }
}

就这样只要我们业务中有需要加锁的业务方法上面只需要加上@RedisLock注解就可以,防止重复触发方法了。

可以使用jmeter进行压力测试。

注意:标注@RedisLock注解的方法需要至少有一个参数,一般在service中的非静态方法上使用

github上翻译的spring framework 中文文档,有点老了,可以看看:https://github.com/waylau/spring-framework-4-reference

### 实现自定义注解以同步Redis和数据库的数据 为了实现通过自定义注解来保持 Redis 和数据库之间的数据一致性,可以通过 AOP(面向切面编程)、分布式以及事件监听机制完成这一目标。以下是详细的解决方案: #### 1. 自定义注解设计 首先,定义一个用于标记需要同步操作的方法的注解 `@SyncData`。 ```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 SyncData { String keyPrefix() default ""; // Redis 的键前缀 } ``` 此注解允许开发者指定 Redis 键的前缀以便于区分不同的业务场景[^3]。 --- #### 2. 配置 AOP 切面逻辑 利用 Spring AOP,在方法执行前后拦截并处理 Redis 数据库同步的操作。 ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @Aspect @Component public class DataSyncAspect { @Autowired private RedisTemplate<Object, Object> redisTemplate; @Around("@annotation(syncData)") public Object syncDataToRedis(ProceedingJoinPoint joinPoint, SyncData syncData) throws Throwable { try { // 执行原始方法获取返回值 Object result = joinPoint.proceed(); // 获取注解中的参数 String keyPrefix = syncData.keyPrefix(); if (keyPrefix.isEmpty()) { throw new IllegalArgumentException("Key prefix cannot be empty"); } // 构造 Redis Key 并存储数据 String redisKey = generateRedisKey(keyPrefix); redisTemplate.opsForValue().set(redisKey, result); return result; } catch (Exception e) { throw e; } } private String generateRedisKey(String keyPrefix) { return keyPrefix + ":data"; } } ``` 上述代码实现了在方法调用完成后自动将结果写入 Redis 中的功能,并支持动态生成 Redis Key[^4]。 --- #### 3. 使用分布式保障线程安全 为了避免并发环境下可能引发的一致性问题,可以在更新 Redis 或者数据库时引入分布式。 ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; @Component public class DistributedLockService { @Autowired private RedissonClient redissonClient; public void executeWithLock(String lockName, Runnable task) { RLock lock = redissonClient.getLock(lockName); try { boolean isLocked = lock.tryLock(10, 5, TimeUnit.SECONDS); if (!isLocked) { throw new RuntimeException("Failed to acquire distributed lock."); } task.run(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } } ``` 该服务封装了基于 Redission 的分布式功能,确保每次只有一条线程能够修改共享资源[^5]。 --- #### 4. 整合流程示例 下面是一个完整的例子展示如何使用这些组件协同工作。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private DistributedLockService distributedLockService; @SyncData(keyPrefix = "user") public User getUserById(Long id) { return userRepository.findById(id).orElse(null); } @SyncData(keyPrefix = "user") public User updateUser(User user) { final String lockKey = "update_user:" + user.getId(); distributedLockService.executeWithLock(lockKey, () -> { userRepository.save(user); }); return user; } } ``` 在此案例中,当调用 `getUserById` 方法查询用户信息或者调用 `updateUser` 更新用户资料时,都会触发对应的 Redis 同步行为[^6]。 --- #### 总结 以上方案综合运用了自定义注解AOP 技术以及分布式技术,有效解决了 Redis 和数据库之间数据一致性的难题。它不仅简化了开发过程还增强了系统的稳定性和可靠性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值