Spring SPEL,自定義注解實現(xiàn)分布式鎖

1. 自定義注解實現(xiàn)分布式鎖

利用自定義注解實現(xiàn)分布式鎖,最麻煩的地方就是,加鎖的key,怎么獲取讲坎,之前項目中,對key的處理是:在調用需要加分布式鎖方法前,就把key拼裝完畢,然后在需要加鎖的方法的第一個參數(shù)傳入key,然后在切面類里面通過反射的方式拿到這個key,執(zhí)行鎖獲取束凑。
這種方式對方法的侵入比較大,需要函數(shù)多出一個參數(shù),傳入key,而在方法內部并沒有使用到這個key,看著很不舒服。所以決定通過spel解決這個問題巍实,spel就是 Spring表達式語言全稱為“Spring Expression Language”贺奠,縮寫為“SpEL”,具體的可以搜索下,網(wǎng)上相關的博文很多。

2. SpelUtil

在spring 的基礎之上封裝的用于解析spel表達式的工具方法

import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;

/**
* 解析SPEL 表達式
* @author huxingnan
* @date 2018/5/21 10:51
*/
public class SpelUtil {
 public static String parse(String spel, Method method, Object[] args) {
     //獲取被攔截方法參數(shù)名列表(使用Spring支持類庫)
     LocalVariableTableParameterNameDiscoverer u =
             new LocalVariableTableParameterNameDiscoverer();
     String[] paraNameArr = u.getParameterNames(method);
     //使用SPEL進行key的解析
     ExpressionParser parser = new SpelExpressionParser();
     //SPEL上下文
     StandardEvaluationContext context = new StandardEvaluationContext();
     //把方法參數(shù)放入SPEL上下文中
     for (int i = 0; i < paraNameArr.length; i++) {
         context.setVariable(paraNameArr[i], args[i]);
     }
     return parser.parseExpression(spel).getValue(context, String.class);
 }

 /**
  * 支持 #p0 參數(shù)索引的表達式解析
  * @param rootObject 根對象,method 所在的對象
  * @param spel 表達式
  * @param method 隆嗅,目標方法
  * @param args 方法入?yún)?  * @return 解析后的字符串
  */
 public static String parse(Object rootObject,String spel, Method method, Object[] args) {
     //獲取被攔截方法參數(shù)名列表(使用Spring支持類庫)
     LocalVariableTableParameterNameDiscoverer u =
             new LocalVariableTableParameterNameDiscoverer();
     String[] paraNameArr = u.getParameterNames(method);
     //使用SPEL進行key的解析
     ExpressionParser parser = new SpelExpressionParser();
     //SPEL上下文
     StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);
     //把方法參數(shù)放入SPEL上下文中
     for (int i = 0; i < paraNameArr.length; i++) {
         context.setVariable(paraNameArr[i], args[i]);
     }
     return parser.parseExpression(spel).getValue(context, String.class);
 }
}

3. 自定義注解addLock

import java.lang.annotation.*;

/**
* 自定義注解為了加鎖使用
* Created by zhaosh on 2017/5/12.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AddLock {
 //spel表達式
 String spel() ;
 //log信息
 String logInfo() default "";
}

4. AOP 切面類AddLockAspect

package com.dream.aspect;

import com.dream.annotation.AddLock;
import com.dream.service.AddLockService;
import com.dream.util.SpelUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author hu
*/
@Aspect
@Component
public class AddLockAspect {

 private Logger logger = LoggerFactory.getLogger(getClass());

 @Resource
 private AddLockService addLockService;

 @Pointcut("@annotation(com.dream.annotation.AddLock)")
 public void addLockAnnotationPointcut() {

 }
 @Around(value = "addLockAnnotationPointcut()")
 public Object addKeyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
     //定義返回值
     Object proceed;
     //獲取方法名稱
     String logInfo = getLogInfo(joinPoint);
     //前置方法 開始
     String redisKey = getRediskey(joinPoint);
     logger.info("{}添加redisKey={}",logInfo,redisKey);
     AtomicBoolean lockState = new AtomicBoolean(false);
     try {
         //對key加分布式鎖:1表示加鎖成功,-1表示存在鎖且未過期,-2表示鎖過期但被其它進程搶先
         int addLock = addLockService.addLock(redisKey, 5);

         if (addLock <=0 ) {
             throw new RuntimeException("加鎖失敗:鎖存在");
         }
         logger.info("{},設置redisKey成功,key={}",logInfo, redisKey);
         lockState.set(true);
         // 目標方法執(zhí)行
         proceed = joinPoint.proceed();
     } catch (Exception exception) {
         logger.error("{}REDIS加鎖失敗,key = {},message={}, code = {}", logInfo,redisKey, exception.getMessage(), exception);
         throw exception;
     } finally {
         if (lockState.get()) {
             logger.info("{}清除redisKey={}",logInfo,redisKey);
             addLockService.clearLock(redisKey);
         }
     }
     return proceed;
 }

 /**
  * 獲取 指定 loginfo
  * 需要接口方法聲明處 添加 AddLock 注解
  * 并且 需要填寫 loginfo
  * @param joinPoint 切入點
  * @return logInfo
  */
 private String getLogInfo(ProceedingJoinPoint joinPoint){
     MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
     Method method = methodSignature.getMethod();
     AddLock annotation = AnnotationUtils.findAnnotation(method, AddLock.class);
     if(annotation == null){
         return methodSignature.getName();
     }
     return annotation.logInfo();
 }
 /**
  * 獲取攔截到的請求方法
  * @param joinPoint 切點
  * @return redisKey
  */
 private String getRediskey(ProceedingJoinPoint joinPoint) {
     Signature signature = joinPoint.getSignature();
     MethodSignature methodSignature = (MethodSignature) signature;
     Method targetMethod = methodSignature.getMethod();
     Object target = joinPoint.getTarget();
     Object[] arguments = joinPoint.getArgs();
     AddLock annotation = AnnotationUtils.findAnnotation(targetMethod, AddLock.class);
     String spel=null;
     if(annotation != null){
          spel = annotation.spel();
     }
     return SpelUtil.parse(target,spel, targetMethod, arguments);
 }
}

5. AddLockService

具體實現(xiàn)就不貼出來啦

     
     /**
* @author huxingnan
* @date 2018/5/21 13:33
   */
   public interface AddLockService {
   /**
    * 加鎖 
    * @param redisKey key
    * @param i i秒之后失效
    * @return 1 成功 -1 失敗 -2 失敗
    */
   int addLock(String redisKey, int i);

   /**
    * 清除鎖
    * @param redisKey key
    */
   void clearLock(String redisKey);
   }

6. 使用AddLock

示范1:

@AddLock(spel = "'CreateOutOrder'+#p0.orderCode",logInfo = "日志信息")
public void testAddLock(Order order){

}

示范2:

@AddLock(spel = "'CreateOutOrder'+#order.orderCode",logInfo = "日志信息")
public void testAddLock(Order order){

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末界阁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胖喳,更是在濱河造成了極大的恐慌泡躯,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異较剃,居然都是意外死亡咕别,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門写穴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惰拱,“玉大人,你說我怎么就攤上這事确垫」保” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵删掀,是天一觀的道長翔冀。 經常有香客問我,道長披泪,這世上最難降的妖魔是什么纤子? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮款票,結果婚禮上控硼,老公的妹妹穿的比我還像新娘。我一直安慰自己艾少,他們只是感情好卡乾,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缚够,像睡著了一般幔妨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谍椅,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天误堡,我揣著相機與錄音,去河邊找鬼雏吭。 笑死锁施,一個胖子當著我的面吹牛,可吹牛的內容都是我干的杖们。 我是一名探鬼主播悉抵,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胀莹!你這毒婦竟也來了基跑?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤描焰,失蹤者是張志新(化名)和其女友劉穎媳否,沒想到半個月后栅螟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡篱竭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年力图,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掺逼。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡吃媒,死狀恐怖,靈堂內的尸體忽然破棺而出吕喘,到底是詐尸還是另有隱情赘那,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布氯质,位于F島的核電站募舟,受9級特大地震影響,放射性物質發(fā)生泄漏闻察。R本人自食惡果不足惜拱礁,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辕漂。 院中可真熱鬧呢灶,春花似錦、人聲如沸钉嘹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跋涣。三九已至飒责,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仆潮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工遣臼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留性置,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓揍堰,卻偏偏與公主長得像鹏浅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屏歹,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容