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 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)); + } + + /** + * 创建表达式上下文,默认情况下,将会注册下述变量: + *

+ * + * @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 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() {