AOP的应用之分布式锁

640?wxfrom=5&wx_lazy=1

来源 :csdn | 作者 : 孙琛斌|原文:阅读原文

但还是要简单的说一下主要就是使用redis的setnx(key,value)方法配合del(key)方法,也就是在第一个请求进来的时候执行这个方法,会将一个key放到redis中,如果redis中该key已经存在了那么就返回0,否则返回1,这样当第一个请求处理完后调用del方法,后面的人就可以再进来了,ok用redis实现分布式锁大概就这个逻辑。

然后基于上面的分布式锁的逻辑,如果你要保证方法的原子性操作,那么就要写这样一串if else,多个地方用到的话冗余代码就太多而且代码看上去不美观,那么如何简化呢?这里考虑用到了aop的方法拦截器技术结合自定义注解来实现,下面直接看代码。

先定义两个注解

@Target({ElementType.TYPE, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface NeedLock {    
   String fieldKey();  
}

/**
* @author chenbin.sun
* 动态key需要配置的注解,只有配置了该注解才能够开启动态key功能
*/

@Documented
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyParam {

 /**
  * 如果动态参数在command对象中,那么就需要设置columns的值为command对象中的属性名可以为多个,否则不需要设置该值
  * <p>例1:public void test(@KeyParam({"id"}) MemberCommand member)
  * <p>例2:public void test(@KeyParam({"id","loginName"}) MemberCommand member)
  * <p>例3:public void test(@KeyParam String memberId)
  * @return
  */

 String[] columns() default {};
}


然后编写具体方法,这里实现了aop的方法拦截器,这种效果类似于aop的环绕通知

/**
* @author chenbin.sun
* @description 对分布式系统的方法进行统一加锁解锁(使用了aop的环绕)
* <p> 使用该方法进行加锁,必须在要加锁的方法上使用@NeedLock注解,如果要使用动态key的话需要配合@KeyParam注解
*/

@Service("lockMethodHelper")
public class LockMethodHelper implements MethodInterceptor {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(LockMethodHelper.class);
 
 private Long status = 0L;
 
 public static final String BEFORE_KEY = "LOCK_METHOD_";
 
 @Autowired
 private LotteryRedisManager lotteryRedisManager;
 
 @Override
 public Object invoke(MethodInvocation invocation) throws Throwable {
   Method method = invocation.getMethod();
   Object[] args = invocation.getArguments();
   
   // 执行方法前
   boolean before = before(method, args);
   
   // 加锁失败
   if (!before) {
     return new ResultReturnCommand(ResultReturnCodeEnum.LOTTERY_REDIS_LOCK_FAIL);
     //return new Object();
   }
   
   Object result = null;
   try {
     
     // 通过反射机制调用目标方法
     result = invocation.proceed();
   } catch (Exception e) {
     LOGGER.error("执行目标方法抛出异常",e);
     return new ResultReturnCommand(ResultReturnCodeEnum.LOTTERY_REDIS_UNLOCK_FAIL);
   } finally {
     
     // 执行方法后解锁
     afterReturning(method, args);
   }
   return result;
 }

 /**
  * 方法执行前的加锁操作
  * @param method
  * @param args
  * @return
  * @throws Throwable
  */

 public boolean before(Method method, Object[] args) throws Throwable {
   
   // 构造锁key
   String key = buildRedisKey(method, args);
   if (null == key) {
     return true;
   }
   Long status = lotteryRedisManager.setnx(key, key);
   if (status.equals(status)) {
     return false;
   }
   return true;
 }
 
 /**
  * 方法执行后的解锁操作
  * @param method
  * @param args
  * @return
  * @throws Throwable
  */

 public boolean afterReturning(Method method, Object[] args) throws Throwable {
   
   // 构造锁key
   String key = buildRedisKey(method, args);
   if (null == key) {
     return true;
   }
   lotteryRedisManager.delKeyAndValue(key);
   return true;
 }

 /**
  * 构造锁key
  * @param method
  * @param args
  * @return
  * @throws NoSuchFieldException
  * @throws IllegalAccessException
  */

 private String buildRedisKey(Method method, Object[] args)
     throws NoSuchFieldException, IllegalAccessException
{
   NeedLock needLock = method.getAnnotation(NeedLock.class);
   if (null == needLock) {
     return null;
   }
   
   // 锁的前半部分key
   String key = BEFORE_KEY + needLock.fieldKey();
   
   // 迭代全部参数的注解,根据使用KeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上
   Annotation[][] parameterAnnotations = method.getParameterAnnotations();
   for (int i = 0; i < parameterAnnotations.length; i++) {
     
     // 循环该参数全部注解
     for (Annotation annotation : parameterAnnotations[i]) {
       
       // 当前参数的注解不包含keyparam
       if(!annotation.annotationType().isInstance(KeyParam.class)){
         continue;
       }
       
       // 当前参数的注解包含keyparam,获取注解配置的值
       String[] columns = ((KeyParam)annotation).columns();
       if (columns.length == 0) {
         
         // 普通数据类型直接拼接
         if (null == args[i]) {
           LOGGER.error("动态参数不能为null!");
           throw new RuntimeException("动态参数不能为null!");
         }
         key += args[i];
       }else{
         
         // keyparam的columns值不为null,所以当前参数应该是对象类型
         for (int j = 0; j < columns.length; j++) {
           Class<? extends Object> clasz = args[i].getClass();
           Field declaredField = clasz.getDeclaredField(columns[j]);
           declaredField.setAccessible(true);
           Object value = declaredField.get(clasz);
           key += value;
         }
       }
       
     }
   }
   return key;
 }
}

怎么使用呢?看下面这个例子

@Override
 @NeedLock(fieldKey = LotteryActivityConstants.REDIS_LOCK_MARK)
 public ResultReturnCommand create(@KeyParam String code, @KeyParam Long memLaunchId) {

   // 对活动做相关校验
   ResultReturnCommand resultReturnCommand = lotteryActivityDetailManager.checkLotteryActivity(code);

   if (!LotteryActivityConstants.SUCCESS_CODE.equals(resultReturnCommand.getCode())) {
     return resultReturnCommand;
   }

   // 1根据code查询是否有对应的活动信息
   LotteryActivityCommand lotteryActivityCommand = (LotteryActivityCommand) resultReturnCommand.getData();

   // 2根据活动ID 和发起者ID查询对应的信息
   LotteryMemLaunch lotteryMemLaunch = lotteryMemLaunchDao.findLotteryMemLaunchBycodeActivityIdAndMemberId(lotteryActivityCommand.getId(), memLaunchId);

   // 5校验是否瞒足发起活动的条件
   if (!LotteryValidator.checkEffective(lotteryMemLaunch)) {
     return new ResultReturnCommand(ResultReturnCodeEnum.ACTIVITY_NOT_FINISHED);
   }

   // 3插入发起活动记录
   LotteryMemLaunch ret = savelotteryMemLaunch(lotteryActivityCommand, memLaunchId);

   return new ResultReturnCommand(ResultReturnCodeEnum.SUCCESS, ret);

 }

只要在方法上加上@NeedLock,然后给个锁的key就可以了,如果你的锁的key是动态的,那么在方法的参数上结合使用@KeyParam注解就可以了,详细使用方法请看最上面,注解的注释即可。

要真正使用这个东西还有一点必不可少的东西就是要在spring的配置中配置aop的拦截范围,配置如下

<!-- 统一加锁aop方法 -->
 <aop:config>
     <aop:pointcut expression="execution(* com.chenbin.sun.plugin.lottery.red.packet.manager..*(..))" id="lockMethodPointcut"/>
     <aop:advisor advice-ref="lockMethodHelper" pointcut-ref="lockMethodPointcut"/>
 </aop:config>

本文只是描述一个思想,具体的redis锁实现方式有很多,上面只是实现了一种最简单的锁,不够严谨,没有家失效时间等,大家使用的话可根据自己的应用场景进行相应的改造

推荐阅读
【重要】公众号送书&【星球福利】
Java工程师成神之路(2018修订版)

640?

猜你喜欢

转载自blog.csdn.net/b644rofp20z37485o35m/article/details/80062799