科技

Spring SPEL,自定義註解實現分散式鎖

1. 自定義註解實現分散式鎖

利用自定義註解實現分散式鎖,最麻煩的地方就是,加鎖的key,怎麼獲取,之前專案中,對key的處理是:在呼叫需要加分散式鎖方法前,就把key拼裝完畢,然後在需要加鎖的方法的第一個引數傳入key,然後在切面類裡面通過反射的方式拿到這個key,執行鎖獲取。

這種方式對方法的侵入比較大,需要函式多出一個引數,傳入key,而在方法內部並沒有使用到這個key,看著很不舒服。所以決定通過spel解決這個問題,spel就是 Spring表示式語言全稱為“Spring Expression Language”,縮寫為“SpEL”,具體的可以搜尋下,網上相關的博文很多。

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) {

//獲取被攔截方法引數名列表(使用Spring支援類庫)

LocalVariableTableParameterNameDiscoverer u =

new LocalVariableTableParameterNameDiscoverer();

String[] paraNameArr = u.getParameterNames(method);

//使用SPEL進行key的解析

ExpressionParser parser = new SpelExpressionParser();

//SPEL上下文

StandardEvaluationContext context = new StandardEvaluationContext();

//把方法引數放入SPEL上下文中

for (int i = 0; i context.setVariable(paraNameArr[i], args[i]);

}

return parser.parseExpression(spel).getValue(context, String.class);

}

/**

* 支援 #p0 引數索引的表示式解析

* @param rootObject 根物件,method 所在的物件

* @param spel 表示式

* @param method ,目標方法

* @param args 方法入參

* @return 解析後的字串

*/

public static String parse(Object rootObject,String spel, Method method, Object[] args) {

//獲取被攔截方法引數名列表(使用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);

//把方法引數放入SPEL上下文中

for (int i = 0; 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()

@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("",logInfo,redisKey);

AtomicBoolean lockState = new AtomicBoolean(false);

try {

//對key加分散式鎖:1表示加鎖成功,-1表示存在鎖且未過期,-2表示鎖過期但被其它程序搶先

int addLock = addLockService.addLock(redisKey, 5);

if (addLock throw new RuntimeException("加鎖失敗:鎖存在");

}

logger.info("",logInfo, redisKey);

lockState.set(true);

// 目標方法執行

proceed = joinPoint.proceed();

} catch (Exception exception) REDIS加鎖失敗,key = , code = {}", logInfo,redisKey, exception.getMessage(), exception);

throw exception;

} finally {

if (lockState.get())

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

具體實現就不貼出來啦

/**

* @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)

Reference:科技日報

看更多!請加入我們的粉絲團

轉載請附文章網址

不可錯過的話題