diff --git a/lock4j-core/src/main/java/com/baomidou/lock/annotation/Lock4j.java b/lock4j-core/src/main/java/com/baomidou/lock/annotation/Lock4j.java
index 039844b22700fda62f26524e5b0e365c112abe04..4d80a33bdbdbfc945a5107b53b4147ba66ed62b8 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/annotation/Lock4j.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/annotation/Lock4j.java
@@ -20,25 +20,30 @@ import com.baomidou.lock.LockFailureStrategy;
import com.baomidou.lock.LockKeyBuilder;
import com.baomidou.lock.executor.LockExecutor;
import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;
+import org.springframework.core.Ordered;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
/**
* 分布式锁注解
*
* @author zengzhihong TaoYu
*/
+// TODO 是否允许作为元注解使用,从而支持Spring组合注解机制
+@Repeatable(Lock4j.List.class)
@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Lock4j {
+ /**
+ * 应用条件表达式,当执行结果为{@code true}或{@code 'true'}时,才会执行锁操作
+ *
+ * @return 名称
+ */
+ String condition() default "";
+
/**
* 用于多个方法锁同一把锁 可以理解为锁资源名称 为空则会使用 包名+类名+方法名
*
@@ -95,4 +100,18 @@ public @interface Lock4j {
*/
Class extends LockKeyBuilder> keyBuilderStrategy() default LockKeyBuilder.class;
+ /**
+ * 获取顺序,值越小越先执行
+ *
+ * @return 顺序值
+ */
+ int order() default Ordered.LOWEST_PRECEDENCE;
+
+ @Target(value = {ElementType.METHOD})
+ @Retention(value = RetentionPolicy.RUNTIME)
+ @Inherited
+ @Documented
+ @interface List {
+ Lock4j[] value();
+ }
}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockChainInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockChainInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ca5b345dd052f246fdf7f996c47fb9c0dc7d0e7
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockChainInterceptor.java
@@ -0,0 +1,112 @@
+package com.baomidou.lock.aop;
+
+import com.baomidou.lock.annotation.Lock4j;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.lang.Nullable;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ *
支持多个{@link Lock4j}注解的锁操作拦截器,
+ * 当执行时,将会遵循{@link Lock4j}注解的依次进行加锁和解锁操作。
+ * 例如:
+ *
+ * @Lock4j(key = "key1", order = 0)
+ * @Lock4j(key = "key2, order = 1)
+ * @Lock4j(key = "key3, order = 2)
+ * public void method() {
+ * // do something
+ * }
+ *
+ * 当执行时,将依次加锁key1、key2、key3,执行完毕后依次解锁key1、key2、key3。
+ *
+ * 中断
+ *
多级锁操作链将根据某一环节是否未获取到锁而决定是否中断,若中断则会依次解锁已加锁的锁。
+ * 比如,若获取key3锁时失败,则会调用失败处理策略,随后解锁key2,与key1。
+ *
+ *
条件
+ *
多级锁的条件是彼此独立的,
+ * 比如,若Key2的表达式执行结果为true时,其他表达式执行结果为false,此时将会正常获取Key1与Key2的锁。
+ * 同理,若Key2的表达式为false,Key3的表达式为true,此时将会正常获取Key1与Key3的锁
+ *
+ * @author huangchengxing
+ */
+public abstract class AbstractConditionalLockChainInterceptor extends AbstractConditionalLockInterceptor {
+
+ /**
+ * 创建一个新的{@link AbstractConditionalLockInterceptor}实例。
+ *
+ * @param expressionParser 表达式解析器
+ * @param parameterNameDiscoverer 参数名发现器
+ */
+ protected AbstractConditionalLockChainInterceptor(
+ ExpressionParser expressionParser, ParameterNameDiscoverer parameterNameDiscoverer) {
+ super(expressionParser, parameterNameDiscoverer);
+ }
+
+ /**
+ * 获取锁操作信息
+ *
+ * @param invocation 方法调用
+ * @return 锁操作信息,若不存在则返回null
+ * @see #createLockOps
+ */
+ @Nullable
+ @Override
+ protected LockOps resolveLockOps(MethodInvocation invocation) {
+ Set ops = AnnotatedElementUtils.findMergedRepeatableAnnotations(invocation.getMethod(), Lock4j.class).stream()
+ .map(this::createLockOps)
+ .sorted(AnnotationAwareOrderComparator.INSTANCE)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ if (CollectionUtils.isEmpty(ops)) {
+ return null;
+ }
+ if (ops.size() == 1) {
+ return ops.iterator().next();
+ }
+ return getMultiLock(ops);
+ }
+
+ /**
+ * 获取由多个{@link LockOps}组成的联锁
+ *
+ * @param ops 锁操作
+ * @return 锁操作链
+ */
+ protected LockOps getMultiLock(Set ops) {
+ Iterator iterator = ops.iterator();
+ LockOpsChain head = new LockOpsChain(iterator.next());
+ LockOpsChain current = head;
+ while (iterator.hasNext()) {
+ current.next = new LockOpsChain(iterator.next());
+ current = current.next;
+ }
+ return head;
+ }
+
+ /**
+ * 由多个{@link LockOps}组成的链
+ */
+ protected static class LockOpsChain extends AbstractLockOpsDelegate {
+ @Nullable
+ private LockOpsChain next;
+ public LockOpsChain(LockOps delegate) {
+ super(delegate);
+ }
+ @Override
+ public MethodInvocation attach(MethodInvocation invocation) {
+ return Objects.isNull(next) ?
+ delegate.attach(invocation) :
+ delegate.attach(next.attach(invocation));
+ }
+ }
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d3e47516f0a36f98dab1fee14f213132a750ebd
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java
@@ -0,0 +1,162 @@
+package com.baomidou.lock.aop;
+
+import com.baomidou.lock.annotation.Lock4j;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.EmbeddedValueResolverAware;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.context.expression.MapAccessor;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.BeanResolver;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * 支持条件表达式的{@link LockOpsInterceptor}扩展类
+ *
+ * @author huangchengxing
+ */
+@RequiredArgsConstructor
+public abstract class AbstractConditionalLockInterceptor
+ extends AbstractLockInterceptor implements EmbeddedValueResolverAware {
+
+ private static final MapAccessor MAP_ACCESSOR = new MapAccessor();
+ private static final Map EXPRESSION_CACHE = new ConcurrentReferenceHashMap<>(32);
+
+ /**
+ * 表达式解析器
+ */
+ private final ExpressionParser expressionParser;
+
+ /**
+ * 参数名发现器
+ */
+ private final ParameterNameDiscoverer parameterNameDiscoverer;
+
+ /**
+ * 值解析器
+ */
+ @Setter
+ private StringValueResolver embeddedValueResolver;
+
+ /**
+ * Bean解析器
+ */
+ private BeanResolver beanResolver;
+
+ /**
+ * Spring上下文
+ *
+ * @param applicationContext Spring上下文
+ */
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ super.setApplicationContext(applicationContext);
+ this.beanResolver = new BeanFactoryResolver(applicationContext);
+ }
+
+ /**
+ * 获取锁操作信息
+ *
+ * @param annotation 注解
+ * @return 锁操作信息
+ */
+ @Override
+ protected LockOps createLockOps(Lock4j annotation) {
+ LockOps delegate = super.createLockOps(annotation);
+ String condition = annotation.condition();
+ if (!StringUtils.hasText(condition)) {
+ return delegate;
+ }
+ condition = embeddedValueResolver.resolveStringValue(condition);
+ return new ConditionalLockOps(delegate, condition);
+ }
+
+ /**
+ * 执行解析条件表达式
+ *
+ * @param condition 条件表达式
+ * @param lockOps 锁操作
+ * @param invocation 方法调用
+ * @return 是否满足条件
+ */
+ protected final boolean evaluateCondition(String condition, LockOps lockOps, MethodInvocation invocation) {
+ // TODO 将此部分逻辑提取至公共组件 MethodBasedExpressionEvaluator,DefaultLockKeyBuilder 亦同
+ Expression expression = EXPRESSION_CACHE.computeIfAbsent(
+ condition, key -> expressionParser.parseExpression(condition)
+ );
+ StandardEvaluationContext context = createEvaluationContext(invocation, lockOps);
+ return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));
+ }
+
+ /**
+ * 创建表达式上下文,默认情况下,将会注册下述变量:
+ *
+ * - {@code rootObject}:{@link MethodInvocation}对象;
+ * - {@code #p0, #p1...}:方法的调用参数;
+ * - {@code #参数名1, #参数名2...}:方法的调用参数;
+ *
+ *
+ * @param invocation 方法调用
+ * @param lockOps 锁操作
+ * @return 表达式上下文
+ */
+ @SuppressWarnings("unused")
+ protected StandardEvaluationContext createEvaluationContext(
+ MethodInvocation invocation, LockOps lockOps) {
+ StandardEvaluationContext context = new StandardEvaluationContext();
+ context.setBeanResolver(beanResolver);
+ context.setRootObject(invocation);
+ context.addPropertyAccessor(MAP_ACCESSOR);
+ registerInvocationArguments(invocation.getMethod(), invocation.getArguments(), context);
+ return context;
+ }
+
+ private void registerInvocationArguments(
+ Method method, Object[] arguments, StandardEvaluationContext context) {
+ if (ObjectUtils.isEmpty(arguments)) {
+ return;
+ }
+ // 注册参数,格式为#p1, #p2...
+ for (int i = 0; i < arguments.length; i++) {
+ context.setVariable("p" + i, arguments[i]);
+ }
+ // 注册参数,格式为#参数名1, #参数名2...
+ String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
+ if (!ObjectUtils.isEmpty(parameterNames)) {
+ for (int i = 0; i < parameterNames.length; i++) {
+ context.setVariable(parameterNames[i], arguments[i]);
+ }
+ }
+ }
+
+ /**
+ * 携带有条件表达式的锁操作信息
+ *
+ * @author huangchengxing
+ */
+ @Getter
+ protected class ConditionalLockOps extends AbstractLockOpsDelegate {
+ private final String condition;
+ public ConditionalLockOps(LockOps delegate, String condition) {
+ super(delegate);
+ this.condition = condition;
+ }
+ @Override
+ public MethodInvocation attach(MethodInvocation invocation) {
+ return evaluateCondition(condition, delegate, invocation) ?
+ delegate.attach(invocation) : invocation;
+ }
+ }
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..90af95b07946f0a5628c714574ee34981efb14da
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java
@@ -0,0 +1,307 @@
+package com.baomidou.lock.aop;
+
+import com.baomidou.lock.LockFailureStrategy;
+import com.baomidou.lock.LockKeyBuilder;
+import com.baomidou.lock.annotation.Lock4j;
+import com.baomidou.lock.util.ThrowableFunction;
+import lombok.*;
+import lombok.experimental.Accessors;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * 锁拦截器抽象类,用于提供关于配置信息的解析的基本实现
+ *
+ * @author huangchengxing
+ */
+@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+public abstract class AbstractLockInterceptor
+ implements InitializingBean, ApplicationContextAware, Lock4jMethodInterceptor {
+
+ /**
+ * 空占位符
+ */
+ private static final LockOps NULL = new NullLockOps();
+
+ /**
+ * 锁操作信息缓存
+ */
+ private final Map lockOpsCaches = new ConcurrentReferenceHashMap<>(16);
+
+ /**
+ * Spring上下文
+ */
+ @Setter
+ protected ApplicationContext applicationContext;
+
+ /**
+ * 默认的锁操作配置
+ */
+ private LockOps defaultLockOps;
+
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ Object target = invocation.getThis();
+ if (Objects.isNull(target)) {
+ return invocation.proceed();
+ }
+ Class> cls = AopProxyUtils.ultimateTargetClass(target);
+ if (!cls.equals(invocation.getThis().getClass())) {
+ return invocation.proceed();
+ }
+ LockOps lockOps = lockOpsCaches.computeIfAbsent(
+ invocation.getMethod(),
+ method -> Optional.ofNullable(resolveLockOps(invocation)).orElse(NULL)
+ );
+ MethodInvocation invocationWithLock = lockOps.attach(invocation);
+ return invocationWithLock.proceed();
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ this.defaultLockOps = initDefaultLockOps();
+ Assert.notNull(applicationContext, "ApplicationContext must not be null");
+ }
+
+ /**
+ * 获取锁操作信息
+ *
+ * @param invocation 方法调用
+ * @return 锁操作信息,若不存在则返回null
+ * @see #createLockOps
+ */
+ @Nullable
+ protected LockOps resolveLockOps(MethodInvocation invocation) {
+ Lock4j annotation = AnnotatedElementUtils.findMergedAnnotation(invocation.getMethod(), Lock4j.class);
+ return Objects.nonNull(annotation) ? createLockOps(annotation) : null;
+ }
+
+ /**
+ * 获取锁操作信息
+ *
+ * @param annotation 注解
+ * @return 锁操作信息
+ */
+ protected LockOps createLockOps(Lock4j annotation) {
+ // TODO 支持根据 beanName 获取相应组件
+ // 获取key构建器,若未在注解中指定,则遵循默认的全局配置
+ LockKeyBuilder keyBuilder = Optional.ofNullable(annotation.keyBuilderStrategy())
+ .filter(type -> !Objects.equals(type, LockKeyBuilder.class))
+ .map(applicationContext::getBeansOfType)
+ .map(Map::values)
+ .flatMap(components -> components.stream().min(AnnotationAwareOrderComparator.INSTANCE))
+ .map(LockKeyBuilder.class::cast)
+ .orElse(defaultLockOps.getLockKeyBuilder());
+ // 获取失败回调策略,若未在注解中指定,则遵循默认的全局配置
+ LockFailureStrategy failureStrategy = Optional.ofNullable(annotation.failStrategy())
+ .filter(type -> !Objects.equals(type, LockFailureStrategy.class))
+ .map(applicationContext::getBeansOfType)
+ .map(Map::values)
+ .flatMap(components -> components.stream().min(AnnotationAwareOrderComparator.INSTANCE))
+ .map(LockFailureStrategy.class::cast)
+ .orElse(defaultLockOps.getLockFailureStrategy());
+ return new LockOpsImpl(annotation)
+ .setLockKeyBuilder(keyBuilder)
+ .setLockFailureStrategy(failureStrategy);
+ }
+
+ /**
+ * 初始化默认的锁操作配置
+ *
+ * @return 默认的锁操作配置
+ */
+ @NonNull
+ protected abstract LockOps initDefaultLockOps();
+
+ /**
+ * 获取锁,并执行方法调用。实现类需实现该方法,以实现具体加锁和解锁逻辑。
+ *
+ * @param lockOps 锁操作
+ * @param invocation 方法调用
+ * @return 锁信息
+ * @throws Throwable 调用异常
+ */
+ protected abstract Object doLock(LockOps lockOps, MethodInvocation invocation) throws Throwable;
+
+ /**
+ * 锁操作
+ *
+ * @author huangchengxing
+ * @see #createLockOps
+ * @see NullLockOps
+ * @see AbstractLockOpsDelegate
+ */
+ protected interface LockOps extends Ordered {
+
+ /**
+ * 获取锁注解
+ *
+ * @return 注解
+ */
+ Lock4j getAnnotation();
+
+ /**
+ * 获取锁构建器
+ *
+ * @return 构建器
+ */
+ LockKeyBuilder getLockKeyBuilder();
+
+ /**
+ * 获取锁失败处理策略
+ *
+ * @return 策略
+ */
+ LockFailureStrategy getLockFailureStrategy();
+
+ /**
+ * 获取顺序
+ *
+ * @return 顺序值
+ */
+ @Override
+ default int getOrder() {
+ return getAnnotation().order();
+ }
+
+ /**
+ * 包装方法调用
+ *
+ * @param invocation 方法调用
+ * @return 方法调用
+ */
+ default MethodInvocation attach(MethodInvocation invocation) {
+ return invocation;
+ }
+ }
+
+ /**
+ * 空操作,用于占位
+ */
+ private static class NullLockOps implements LockOps {
+ @Override
+ public Lock4j getAnnotation() {
+ return null;
+ }
+ @Override
+ public LockKeyBuilder getLockKeyBuilder() {
+ return null;
+ }
+ @Override
+ public LockFailureStrategy getLockFailureStrategy() {
+ return null;
+ }
+ }
+
+ /**
+ * 默认的锁操作实现
+ */
+ @Accessors(chain = true)
+ @Getter
+ @Setter
+ @RequiredArgsConstructor
+ @EqualsAndHashCode(onlyExplicitlyIncluded = true)
+ protected class LockOpsImpl implements LockOps {
+
+ /**
+ * 锁注解
+ */
+ @EqualsAndHashCode.Include
+ private final Lock4j annotation;
+
+ /**
+ * key生成器
+ */
+ private LockKeyBuilder lockKeyBuilder;
+
+ /**
+ * 锁失败策略
+ */
+ private LockFailureStrategy lockFailureStrategy;
+
+ /**
+ * 顺序
+ */
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ /**
+ * 包装方法调用
+ *
+ * @param invocation 方法调用
+ * @return 方法调用
+ */
+ @Override
+ public MethodInvocation attach(MethodInvocation invocation) {
+ return new DelegatedMethodInvocation(invocation, inv -> doLock(this, inv));
+ }
+ }
+
+ /**
+ * 锁操作信息委托类
+ */
+ @RequiredArgsConstructor
+ protected abstract static class AbstractLockOpsDelegate implements LockOps {
+ protected final LockOps delegate;
+ @Override
+ public Lock4j getAnnotation() {
+ return delegate.getAnnotation();
+ }
+ @Override
+ public LockKeyBuilder getLockKeyBuilder() {
+ return delegate.getLockKeyBuilder();
+ }
+ @Override
+ public LockFailureStrategy getLockFailureStrategy() {
+ return delegate.getLockFailureStrategy();
+ }
+ @Override
+ public int getOrder() {
+ return delegate.getOrder();
+ }
+ }
+
+ @RequiredArgsConstructor
+ private static class DelegatedMethodInvocation implements MethodInvocation {
+ private final MethodInvocation delegate;
+ private final ThrowableFunction invoker;
+ @Override
+ public Object proceed() throws Throwable {
+ return invoker.apply(delegate);
+ }
+ @Override
+ public Object getThis() {
+ return delegate.getThis();
+ }
+ @NonNull
+ @Override
+ public AccessibleObject getStaticPart() {
+ return delegate.getStaticPart();
+ }
+ @NonNull
+ @Override
+ public Method getMethod() {
+ return delegate.getMethod();
+ }
+ @NonNull
+ @Override
+ public Object[] getArguments() {
+ return delegate.getArguments();
+ }
+ }
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/Lock4jMethodInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/Lock4jMethodInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..29796942758b059cd257ea91f0570b34f6476be2
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/Lock4jMethodInterceptor.java
@@ -0,0 +1,13 @@
+package com.baomidou.lock.aop;
+
+import org.aopalliance.intercept.MethodInterceptor;
+
+/**
+ * 基于{@link com.baomidou.lock.annotation.Lock4j}注解的方法拦截器
+ *
+ * @author huangchengxing
+ * @see LockInterceptor
+ * @see LockOpsInterceptor
+ */
+public interface Lock4jMethodInterceptor extends MethodInterceptor {
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockAnnotationAdvisor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockAnnotationAdvisor.java
index 498dab5b4602429a3c1d11e58c3f940f02633190..e474ba936e455e57406f50b5c3a5cd89b51576b1 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockAnnotationAdvisor.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockAnnotationAdvisor.java
@@ -24,8 +24,8 @@ import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
+import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.StaticMethodMatcher;
-import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -43,11 +43,12 @@ import java.lang.reflect.Proxy;
*/
public class LockAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
- private final Advice advice;
+ private final Lock4jMethodInterceptor advice;
- private final Pointcut pointcut = new AnnotationMethodPoint(Lock4j.class);
+ private final Pointcut pointcut = new ComposablePointcut(new AnnotationMethodPoint(Lock4j.class))
+ .union(new AnnotationMethodPoint(Lock4j.List.class));
- public LockAnnotationAdvisor(@NonNull LockInterceptor lockInterceptor, int order) {
+ public LockAnnotationAdvisor(@NonNull Lock4jMethodInterceptor lockInterceptor, int order) {
this.advice = lockInterceptor;
setOrder(order);
}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockInterceptor.java
index b034bc84d30f64e0599872b8851651a4fef1bb25..d1fb709aa05933fb6178342d89f02ada352724ad 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockInterceptor.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockInterceptor.java
@@ -25,7 +25,6 @@ import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
@@ -47,7 +46,7 @@ import java.util.stream.Collectors;
*/
@Slf4j
@RequiredArgsConstructor
-public class LockInterceptor implements MethodInterceptor,InitializingBean {
+public class LockInterceptor implements InitializingBean,Lock4jMethodInterceptor {
private final Map, LockKeyBuilder> keyBuilderMap = new LinkedHashMap<>();
private final Map, LockFailureStrategy> failureStrategyMap = new LinkedHashMap<>();
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a8dfb85bfe22b27737661923a9ce196e686177b
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java
@@ -0,0 +1,128 @@
+package com.baomidou.lock.aop;
+
+import com.baomidou.lock.LockFailureStrategy;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockKeyBuilder;
+import com.baomidou.lock.LockTemplate;
+import com.baomidou.lock.annotation.Lock4j;
+import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * 基于{@link Lock4j}注解的锁操作拦截器
+ *
+ * @author huangchengxing
+ */
+@Slf4j
+public class LockOpsInterceptor extends AbstractConditionalLockChainInterceptor {
+
+ private final LockTemplate lockTemplate;
+ protected final Lock4jProperties lock4jProperties;
+
+ public LockOpsInterceptor(
+ ExpressionParser expressionParser, ParameterNameDiscoverer parameterNameDiscoverer,
+ LockTemplate lockTemplate, Lock4jProperties lock4jProperties) {
+ super(expressionParser, parameterNameDiscoverer);
+ this.lockTemplate = lockTemplate;
+ this.lock4jProperties = lock4jProperties;
+ }
+
+ public LockOpsInterceptor(
+ Lock4jProperties lock4jProperties, LockTemplate lockTemplate) {
+ this(new SpelExpressionParser(), new DefaultParameterNameDiscoverer(), lockTemplate, lock4jProperties);
+ }
+
+ /**
+ * 初始化默认的锁操作配置
+ *
+ * @return 默认的锁操作配置
+ */
+ @NonNull
+ @Override
+ protected LockOps initDefaultLockOps() {
+ LockKeyBuilder lockKeyBuilder = obtainComponent(LockKeyBuilder.class, lock4jProperties.getPrimaryKeyBuilder());
+ LockFailureStrategy lockFailureStrategy = obtainComponent(LockFailureStrategy.class, lock4jProperties.getPrimaryFailureStrategy());
+ return new LockOpsImpl(null)
+ .setLockKeyBuilder(lockKeyBuilder)
+ .setLockFailureStrategy(lockFailureStrategy);
+ }
+
+ private C obtainComponent(Class type, @Nullable Class extends C> defaultType) {
+ // TODO 支持根据 beanName 获取相应组件
+ if (Objects.nonNull(defaultType)) {
+ return applicationContext.getBean(defaultType);
+ }
+ Collection components = applicationContext.getBeansOfType(type).values();
+ return components.stream()
+ .min(AnnotationAwareOrderComparator.INSTANCE)
+ .orElseThrow(() -> new IllegalArgumentException("No component of type " + type.getName() + " found"));
+ }
+
+ /**
+ * 进行加锁
+ *
+ * @param lockOps 锁操作
+ * @param invocation 方法调用
+ * @return 锁信息
+ * @throws Throwable 调用异常
+ */
+ @Override
+ protected Object doLock(LockOps lockOps, MethodInvocation invocation) throws Throwable {
+ Lock4j annotation = lockOps.getAnnotation();
+ LockInfo lockInfo = null;
+ try {
+ String key = resolveKey(invocation, lockOps);
+ lockInfo = lockTemplate.lock(key, annotation.expire(), annotation.acquireTimeout(), annotation.executor());
+ if (Objects.nonNull(lockInfo)) {
+ log.debug("Lock success, lockKey={}, lockValue={}", lockInfo.getLockKey(), lockInfo.getLockValue());
+ return invocation.proceed();
+ }
+ log.debug("Lock failure, lockKey={}", key);
+ // lock failure
+ lockOps.getLockFailureStrategy()
+ .onLockFailure(key, invocation.getMethod(), invocation.getArguments());
+ return null;
+ } finally {
+ doUnlock(lockInfo, annotation);
+ }
+ }
+
+ private void doUnlock(@Nullable LockInfo lockInfo, Lock4j annotation) {
+ if (Objects.isNull(lockInfo) || annotation.autoRelease()) {
+ return;
+ }
+ final boolean releaseLock = lockTemplate.releaseLock(lockInfo);
+ if (!releaseLock) {
+ log.error("Release lock fail, lockKey={}, lockValue={}", lockInfo.getLockKey(),
+ lockInfo.getLockValue());
+ }
+ log.debug("Release lock success, lockKey={}, lockValue={}", lockInfo.getLockKey(),
+ lockInfo.getLockValue());
+ }
+
+ private String resolveKey(MethodInvocation invocation, LockOps lockOps) {
+ String prefix = lock4jProperties.getLockKeyPrefix() + ":";
+ Method method = invocation.getMethod();
+ Lock4j annotation = lockOps.getAnnotation();
+ prefix += StringUtils.hasText(annotation.name()) ? annotation.name() :
+ method.getDeclaringClass().getName() + method.getName();
+ String key = prefix + "#" + lockOps.getLockKeyBuilder().buildKey(invocation, annotation.keys());
+ if (log.isDebugEnabled()) {
+ log.debug("generate lock key [{}] for invocation of [{}]", key, method);
+ }
+ return key;
+ }
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/LockAutoConfiguration.java b/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/LockAutoConfiguration.java
index cd9471fc6d961fcb196e041db095d18435f88d6a..bc6616947849f1c6729928647e98a3aef7f568da 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/LockAutoConfiguration.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/LockAutoConfiguration.java
@@ -17,8 +17,9 @@
package com.baomidou.lock.spring.boot.autoconfigure;
import com.baomidou.lock.*;
+import com.baomidou.lock.aop.Lock4jMethodInterceptor;
import com.baomidou.lock.aop.LockAnnotationAdvisor;
-import com.baomidou.lock.aop.LockInterceptor;
+import com.baomidou.lock.aop.LockOpsInterceptor;
import com.baomidou.lock.executor.LocalLockExecutor;
import com.baomidou.lock.executor.LockExecutor;
import org.springframework.beans.factory.BeanFactory;
@@ -26,7 +27,6 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
@@ -74,16 +74,16 @@ public class LockAutoConfiguration {
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
- @ConditionalOnMissingBean
- public LockInterceptor lockInterceptor(@Lazy LockTemplate lockTemplate, List keyBuilders,
- List failureStrategies, Lock4jProperties properties) {
- return new LockInterceptor(lockTemplate, keyBuilders, failureStrategies, properties);
+ @ConditionalOnMissingBean(Lock4jMethodInterceptor.class)
+ public LockOpsInterceptor conditionalLockOpsInterceptor(
+ Lock4jProperties lock4jProperties, LockTemplate lockTemplate) {
+ return new LockOpsInterceptor(lock4jProperties, lockTemplate);
}
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnMissingBean
- public LockAnnotationAdvisor lockAnnotationAdvisor(LockInterceptor lockInterceptor) {
+ public LockAnnotationAdvisor lockAnnotationAdvisor(Lock4jMethodInterceptor lockInterceptor) {
return new LockAnnotationAdvisor(lockInterceptor, Ordered.HIGHEST_PRECEDENCE);
}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/util/ThrowableFunction.java b/lock4j-core/src/main/java/com/baomidou/lock/util/ThrowableFunction.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9887335b4965f7e7a525ee81b3674a04bd2b11f
--- /dev/null
+++ b/lock4j-core/src/main/java/com/baomidou/lock/util/ThrowableFunction.java
@@ -0,0 +1,17 @@
+package com.baomidou.lock.util;
+
+/**
+ * @author huangchengxing
+ */
+@FunctionalInterface
+public interface ThrowableFunction {
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param t the function argument
+ * @return the function result
+ * @throws Throwable if met with any error
+ */
+ R apply(T t) throws Throwable;
+}
diff --git a/lock4j-test/src/main/java/com/baomidou/lock/test/SpringBootLockTest.java b/lock4j-test/src/main/java/com/baomidou/lock/test/SpringBootLockTest.java
index 3f2008adfb7b700bb8fe348a7c44d42e3250b7b1..ca5e62dc85b051c93ebb2ec20581d2e51b822e06 100644
--- a/lock4j-test/src/main/java/com/baomidou/lock/test/SpringBootLockTest.java
+++ b/lock4j-test/src/main/java/com/baomidou/lock/test/SpringBootLockTest.java
@@ -43,6 +43,17 @@ public class SpringBootLockTest {
SpringApplication.run(SpringBootLockTest.class, args);
}
+ @Test
+ void multiConditionTest() {
+ userService.multiCondition1(24);
+ }
+
+ @Test
+ void conditionTest() {
+ userService.condition1(-1);
+ userService.condition1(1);
+ }
+
@SneakyThrows
@Test
public void localLockTest() {
diff --git a/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserService.java b/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserService.java
index 6faf1b45845ee235dea7f0a96f55e8c8bce737ad..bb0a390e530acbf8d1995aee5414e814f18efb4b 100644
--- a/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserService.java
+++ b/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserService.java
@@ -27,6 +27,10 @@ public interface UserService {
void localLock1(long blockTimeMillis);
+ void multiCondition1(Integer id);
+
+ void condition1(Integer id);
+
void simple1();
void simple2(String myKey);
diff --git a/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserServiceImpl.java b/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserServiceImpl.java
index 0ff4c7f69173d07be99b413d8fd869fffeba440d..a0eaaaf7780ac70ce27f13eb45111afbb27b50a0 100644
--- a/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserServiceImpl.java
+++ b/lock4j-test/src/main/java/com/baomidou/lock/test/service/UserServiceImpl.java
@@ -16,6 +16,8 @@
package com.baomidou.lock.test.service;
+import com.baomidou.lock.DefaultLockFailureStrategy;
+import com.baomidou.lock.DefaultLockKeyBuilder;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
@@ -54,6 +56,40 @@ public class UserServiceImpl implements UserService{
LockSupport.parkNanos(blockTimeMillis * 1000000);
}
+ @Lock4j(
+ condition = "#id % 2 == 0",
+ keys = "#id + '0'", keyBuilderStrategy = DefaultLockKeyBuilder.class,
+ failStrategy = DefaultLockFailureStrategy.class,
+
+ order = 0
+ )
+ @Lock4j(
+ condition = "#id % 4 == 0",
+ keys = "#id + '1'", keyBuilderStrategy = DefaultLockKeyBuilder.class,
+ failStrategy = DefaultLockFailureStrategy.class,
+ order = 1
+ )
+ @Lock4j(
+ condition = "#id % 6 == 0",
+ keys = "#id + '2'", keyBuilderStrategy = DefaultLockKeyBuilder.class,
+ failStrategy = DefaultLockFailureStrategy.class,
+ order = 2
+ )
+ @Override
+ public void multiCondition1(Integer id) {
+ System.out.println("执行multiCondition1方法 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
+ }
+
+ @Lock4j(
+ condition = "#id > 0",
+ keys = "#id", keyBuilderStrategy = DefaultLockKeyBuilder.class,
+ failStrategy = DefaultLockFailureStrategy.class
+ )
+ @Override
+ public void condition1(Integer id) {
+ System.out.println("执行condition1方法 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
+ }
+
@Override
@Lock4j(executor = RedissonLockExecutor.class)
public void simple1() {