From ad394582b36a80a83d6a9ab6369a21a611ac7be8 Mon Sep 17 00:00:00 2001 From: huangchengxingf <841396397@qq.com> Date: Tue, 26 Mar 2024 00:09:29 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=E5=9F=BA=E4=BA=8E=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=96=B9=E6=B3=95=E9=87=8D=E6=9E=84=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=8B=A6=E6=88=AA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lock/aop/AbstractLockOpsInterceptor.java | 256 ++++++++++++++++++ .../aop/ConditionalLockOpsInterceptor.java | 205 ++++++++++++++ .../lock/aop/Lock4jMethodInterceptor.java | 13 + .../lock/aop/LockAnnotationAdvisor.java | 5 +- .../baomidou/lock/aop/LockInterceptor.java | 3 +- .../baomidou/lock/aop/LockOpsInterceptor.java | 81 ++++++ .../autoconfigure/LockAutoConfiguration.java | 14 +- 7 files changed, 565 insertions(+), 12 deletions(-) create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/aop/Lock4jMethodInterceptor.java create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java new file mode 100644 index 0000000..c28e072 --- /dev/null +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java @@ -0,0 +1,256 @@ +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.spring.boot.autoconfigure.Lock4jProperties; +import lombok.*; +import lombok.experimental.SuperBuilder; +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.Method; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * 锁拦截器抽象类,用于提供关于配置信息的解析的基本实现 + * + * @author huangchengxing + */ +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractLockOpsInterceptor + implements InitializingBean, ApplicationContextAware, Lock4jMethodInterceptor { + + /** + * 空占位符 + */ + private static final LockOps NULL = new NullLockOps(); + + /** + * 锁操作信息缓存 + */ + private final Map lockOpsCaches = new ConcurrentReferenceHashMap<>(16); + + /** + * Spring上下文 + */ + @Setter + protected ApplicationContext applicationContext; + + /** + * 配置 + */ + protected final Lock4jProperties lock4jProperties; + + /** + * 默认的锁操作配置 + */ + 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) + ); + return lockOps == NULL ? + invocation.proceed() : doLock(invocation, lockOps); + } + + @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 LockOpsImpl.builder() + .annotation(annotation) + .lockKeyBuilder(keyBuilder) + .lockFailureStrategy(failureStrategy) + .build(); + } + + + /** + * 初始化默认的锁操作配置 + * + * @return 默认的锁操作配置 + */ + @NonNull + protected LockOps initDefaultLockOps() { + LockKeyBuilder lockKeyBuilder = obtainComponent(LockKeyBuilder.class, lock4jProperties.getPrimaryKeyBuilder()); + LockFailureStrategy lockFailureStrategy = obtainComponent(LockFailureStrategy.class, lock4jProperties.getPrimaryFailureStrategy()); + return LockOpsImpl.builder() + .lockKeyBuilder(lockKeyBuilder) + .lockFailureStrategy(lockFailureStrategy) + .build(); + } + + 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 invocation 方法调用 + * @param lockOps 锁操作 + * @return 锁信息 + * @throws Throwable 调用异常 + */ + protected abstract Object doLock(MethodInvocation invocation, LockOps lockOps) throws Throwable; + + /** + * 锁操作 + * + * @author huangchengxing + * @see #createLockOps + */ + protected interface LockOps extends Ordered { + + /** + * 获取锁注解 + * + * @return 注解 + */ + Lock4j getAnnotation(); + + /** + * 获取锁构建器 + * + * @return 构建器 + */ + LockKeyBuilder getLockKeyBuilder(); + + /** + * 获取锁失败处理策略 + * + * @return 策略 + */ + LockFailureStrategy getLockFailureStrategy(); + + /** + * 获取顺序 + * + * @return 顺序值 + */ + @Override + default int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + /** + * 空操作,用于占位 + */ + private static class NullLockOps implements LockOps { + @Override + public Lock4j getAnnotation() { + return null; + } + @Override + public LockKeyBuilder getLockKeyBuilder() { + return null; + } + @Override + public LockFailureStrategy getLockFailureStrategy() { + return null; + } + } + + /** + * 默认的锁操作实现 + */ + @Getter + @SuperBuilder + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + protected static class LockOpsImpl implements LockOps { + + /** + * 锁注解 + */ + @EqualsAndHashCode.Include + private final Lock4j annotation; + + /** + * key生成器 + */ + private LockKeyBuilder lockKeyBuilder; + + /** + * 锁失败策略 + */ + private LockFailureStrategy lockFailureStrategy; + + /** + * 顺序 + */ + @Builder.Default + private int order = Ordered.LOWEST_PRECEDENCE; + } +} diff --git a/lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java new file mode 100644 index 0000000..7c76178 --- /dev/null +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java @@ -0,0 +1,205 @@ +package com.baomidou.lock.aop; + +import com.baomidou.lock.LockTemplate; +import com.baomidou.lock.annotation.Lock4j; +import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Delegate; +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.DefaultParameterNameDiscoverer; +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.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.NonNull; +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 + */ +public class ConditionalLockOpsInterceptor + extends LockOpsInterceptor 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; + + /** + * 创建一个新的{@link ConditionalLockOpsInterceptor}实例。 + * + * @param lock4jProperties 锁配置 + * @param lockTemplate 锁模板 + */ + public ConditionalLockOpsInterceptor( + Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { + this(lock4jProperties, lockTemplate, new SpelExpressionParser(), new DefaultParameterNameDiscoverer()); + } + + /** + * 创建一个新的{@link ConditionalLockOpsInterceptor}实例。 + * + * @param lock4jProperties 锁配置 + * @param lockTemplate 锁模板 + * @param expressionParser 表达式解析器 + * @param parameterNameDiscoverer 参数名发现器 + */ + public ConditionalLockOpsInterceptor( + Lock4jProperties lock4jProperties, LockTemplate lockTemplate, + @NonNull ExpressionParser expressionParser, @NonNull ParameterNameDiscoverer parameterNameDiscoverer) { + super(lock4jProperties, lockTemplate); + this.expressionParser = expressionParser; + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + /** + * 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(); + return StringUtils.hasText(condition) ? + new ConditionalLockOps(delegate, condition) : delegate; + } + + /** + * 进行加锁 + * + * @param invocation 方法调用 + * @param lockOps 锁操作 + * @return 锁信息 + * @throws Throwable 调用异常 + */ + @Override + protected Object doLock(MethodInvocation invocation, LockOps lockOps) throws Throwable { + if (!(lockOps instanceof ConditionalLockOps)) { + return super.doLock(invocation, lockOps); + } + String condition = ((ConditionalLockOps) lockOps).getCondition(); + condition = embeddedValueResolver.resolveStringValue(condition); + if (!evaluateCondition(condition, lockOps, invocation)) { + return invocation.proceed(); + } + return super.doLock(invocation, lockOps); + } + + /** + * 执行解析条件表达式 + * + * @param condition 条件表达式 + * @param lockOps 锁操作 + * @param invocation 方法调用 + * @return 是否满足条件 + */ + protected final boolean evaluateCondition(String condition, LockOps lockOps, MethodInvocation invocation) { + 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 #arg0, #arg1...}:方法的调用参数;
  • + *
  • {@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; + } + // 注册参数,格式为#arg0, #arg1... + for (int i = 0; i < arguments.length; i++) { + context.setVariable("arg" + 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 + @RequiredArgsConstructor + protected static class ConditionalLockOps implements LockOps { + @Delegate + private final LockOps delegate; + private final String condition; + } +} 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 0000000..2979694 --- /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 498dab5..11b44b4 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 @@ -25,7 +25,6 @@ import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.AopUtils; 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 +42,11 @@ 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); - 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 b034bc8..d1fb709 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 0000000..19a9a86 --- /dev/null +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java @@ -0,0 +1,81 @@ +package com.baomidou.lock.aop; + +import com.baomidou.lock.LockInfo; +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.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 基于{@link Lock4j}注解的锁操作拦截器 + * + * @author huangchengxing + */ +@Slf4j +public class LockOpsInterceptor extends AbstractLockOpsInterceptor { + + private final LockTemplate lockTemplate; + + public LockOpsInterceptor( + Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { + super(lock4jProperties); + this.lockTemplate = lockTemplate; + } + + /** + * 进行加锁 + * + * @param invocation 方法调用 + * @param lockOps 锁操作 + * @return 锁信息 + * @throws Throwable 调用异常 + */ + @Override + protected Object doLock(MethodInvocation invocation, LockOps lockOps) 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)) { + return invocation.proceed(); + } + // 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("releaseLock fail,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 cd9471f..6676c21 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.ConditionalLockOpsInterceptor; +import com.baomidou.lock.aop.Lock4jMethodInterceptor; import com.baomidou.lock.aop.LockAnnotationAdvisor; -import com.baomidou.lock.aop.LockInterceptor; 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 ConditionalLockOpsInterceptor conditionalLockOpsInterceptor( + Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { + return new ConditionalLockOpsInterceptor(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); } -- Gitee From 1b2d55b47ca1a4df8da584f62d02283eadc8839e Mon Sep 17 00:00:00 2001 From: huangchengxingf <841396397@qq.com> Date: Tue, 26 Mar 2024 00:09:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E6=8B=A6=E6=88=AA=E5=99=A8?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87=E6=9D=A1=E4=BB=B6=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E5=8A=A8=E6=80=81=E5=88=A4=E6=96=AD=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/baomidou/lock/annotation/Lock4j.java | 15 +++++++++------ .../baomidou/lock/test/SpringBootLockTest.java | 6 ++++++ .../baomidou/lock/test/service/UserService.java | 2 ++ .../lock/test/service/UserServiceImpl.java | 11 +++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) 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 039844b..1ebcca4 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 @@ -21,24 +21,27 @@ import com.baomidou.lock.LockKeyBuilder; import com.baomidou.lock.executor.LockExecutor; import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties; -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组合注解机制 @Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Lock4j { + /** + * 应用条件表达式,当执行结果为{@code true}或{@code 'true'}时,才会执行锁操作 + * + * @return 名称 + */ + String condition() default ""; + /** * 用于多个方法锁同一把锁 可以理解为锁资源名称 为空则会使用 包名+类名+方法名 * 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 3f2008a..79598f9 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,12 @@ public class SpringBootLockTest { SpringApplication.run(SpringBootLockTest.class, args); } + @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 6faf1b4..3d5d90f 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,8 @@ public interface UserService { void localLock1(long blockTimeMillis); + 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 0ff4c7f..d155bc2 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,15 @@ public class UserServiceImpl implements UserService{ LockSupport.parkNanos(blockTimeMillis * 1000000); } + @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() { -- Gitee From deca2cd4bd6b004f146db1cea6e82be6c982dc43 Mon Sep 17 00:00:00 2001 From: huangchengxingf <841396397@qq.com> Date: Fri, 29 Mar 2024 11:08:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8?= =?UTF-8?q?=E5=90=8C=E4=B8=80=E6=96=B9=E6=B3=95=E4=B8=8A=E5=A3=B0=E6=98=8E?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E9=94=81=E6=B3=A8=E8=A7=A3=20#I9D5IK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/baomidou/lock/annotation/Lock4j.java | 16 ++ ...stractConditionalLockChainInterceptor.java | 112 ++++++++++++++ ...> AbstractConditionalLockInterceptor.java} | 83 +++-------- ...ptor.java => AbstractLockInterceptor.java} | 137 ++++++++++++------ .../lock/aop/LockAnnotationAdvisor.java | 4 +- .../baomidou/lock/aop/LockOpsInterceptor.java | 59 +++++++- .../autoconfigure/LockAutoConfiguration.java | 6 +- .../baomidou/lock/util/ThrowableFunction.java | 17 +++ .../lock/test/SpringBootLockTest.java | 5 + .../lock/test/service/UserService.java | 2 + .../lock/test/service/UserServiceImpl.java | 27 +++- 11 files changed, 351 insertions(+), 117 deletions(-) create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockChainInterceptor.java rename lock4j-core/src/main/java/com/baomidou/lock/aop/{ConditionalLockOpsInterceptor.java => AbstractConditionalLockInterceptor.java} (64%) rename lock4j-core/src/main/java/com/baomidou/lock/aop/{AbstractLockOpsInterceptor.java => AbstractLockInterceptor.java} (67%) create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/util/ThrowableFunction.java 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 1ebcca4..4d80a33 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,6 +20,7 @@ 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.*; @@ -29,6 +30,7 @@ import java.lang.annotation.*; * @author zengzhihong TaoYu */ // TODO 是否允许作为元注解使用,从而支持Spring组合注解机制 +@Repeatable(Lock4j.List.class) @Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) @Inherited @@ -98,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 0000000..1ca5b34 --- /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/ConditionalLockOpsInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java similarity index 64% rename from lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java rename to lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java index 7c76178..6d3e475 100644 --- a/lock4j-core/src/main/java/com/baomidou/lock/aop/ConditionalLockOpsInterceptor.java +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractConditionalLockInterceptor.java @@ -1,25 +1,19 @@ package com.baomidou.lock.aop; -import com.baomidou.lock.LockTemplate; import com.baomidou.lock.annotation.Lock4j; -import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import lombok.experimental.Delegate; 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.DefaultParameterNameDiscoverer; 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.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.lang.NonNull; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -33,8 +27,9 @@ import java.util.Map; * * @author huangchengxing */ -public class ConditionalLockOpsInterceptor - extends LockOpsInterceptor implements EmbeddedValueResolverAware { +@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); @@ -60,33 +55,6 @@ public class ConditionalLockOpsInterceptor */ private BeanResolver beanResolver; - /** - * 创建一个新的{@link ConditionalLockOpsInterceptor}实例。 - * - * @param lock4jProperties 锁配置 - * @param lockTemplate 锁模板 - */ - public ConditionalLockOpsInterceptor( - Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { - this(lock4jProperties, lockTemplate, new SpelExpressionParser(), new DefaultParameterNameDiscoverer()); - } - - /** - * 创建一个新的{@link ConditionalLockOpsInterceptor}实例。 - * - * @param lock4jProperties 锁配置 - * @param lockTemplate 锁模板 - * @param expressionParser 表达式解析器 - * @param parameterNameDiscoverer 参数名发现器 - */ - public ConditionalLockOpsInterceptor( - Lock4jProperties lock4jProperties, LockTemplate lockTemplate, - @NonNull ExpressionParser expressionParser, @NonNull ParameterNameDiscoverer parameterNameDiscoverer) { - super(lock4jProperties, lockTemplate); - this.expressionParser = expressionParser; - this.parameterNameDiscoverer = parameterNameDiscoverer; - } - /** * Spring上下文 * @@ -108,29 +76,11 @@ public class ConditionalLockOpsInterceptor protected LockOps createLockOps(Lock4j annotation) { LockOps delegate = super.createLockOps(annotation); String condition = annotation.condition(); - return StringUtils.hasText(condition) ? - new ConditionalLockOps(delegate, condition) : delegate; - } - - /** - * 进行加锁 - * - * @param invocation 方法调用 - * @param lockOps 锁操作 - * @return 锁信息 - * @throws Throwable 调用异常 - */ - @Override - protected Object doLock(MethodInvocation invocation, LockOps lockOps) throws Throwable { - if (!(lockOps instanceof ConditionalLockOps)) { - return super.doLock(invocation, lockOps); + if (!StringUtils.hasText(condition)) { + return delegate; } - String condition = ((ConditionalLockOps) lockOps).getCondition(); condition = embeddedValueResolver.resolveStringValue(condition); - if (!evaluateCondition(condition, lockOps, invocation)) { - return invocation.proceed(); - } - return super.doLock(invocation, lockOps); + return new ConditionalLockOps(delegate, condition); } /** @@ -142,6 +92,7 @@ public class ConditionalLockOpsInterceptor * @return 是否满足条件 */ protected final boolean evaluateCondition(String condition, LockOps lockOps, MethodInvocation invocation) { + // TODO 将此部分逻辑提取至公共组件 MethodBasedExpressionEvaluator,DefaultLockKeyBuilder 亦同 Expression expression = EXPRESSION_CACHE.computeIfAbsent( condition, key -> expressionParser.parseExpression(condition) ); @@ -153,7 +104,7 @@ public class ConditionalLockOpsInterceptor * 创建表达式上下文,默认情况下,将会注册下述变量: *

    *
  • {@code rootObject}:{@link MethodInvocation}对象;
  • - *
  • {@code #arg0, #arg1...}:方法的调用参数;
  • + *
  • {@code #p0, #p1...}:方法的调用参数;
  • *
  • {@code #参数名1, #参数名2...}:方法的调用参数;
  • *
* @@ -177,9 +128,9 @@ public class ConditionalLockOpsInterceptor if (ObjectUtils.isEmpty(arguments)) { return; } - // 注册参数,格式为#arg0, #arg1... + // 注册参数,格式为#p1, #p2... for (int i = 0; i < arguments.length; i++) { - context.setVariable("arg" + i, arguments[i]); + context.setVariable("p" + i, arguments[i]); } // 注册参数,格式为#参数名1, #参数名2... String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); @@ -196,10 +147,16 @@ public class ConditionalLockOpsInterceptor * @author huangchengxing */ @Getter - @RequiredArgsConstructor - protected static class ConditionalLockOps implements LockOps { - @Delegate - private final LockOps delegate; + 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/AbstractLockOpsInterceptor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java similarity index 67% rename from lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java rename to lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java index c28e072..90af95b 100644 --- a/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockOpsInterceptor.java +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/AbstractLockInterceptor.java @@ -3,9 +3,9 @@ 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.spring.boot.autoconfigure.Lock4jProperties; +import com.baomidou.lock.util.ThrowableFunction; import lombok.*; -import lombok.experimental.SuperBuilder; +import lombok.experimental.Accessors; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.InitializingBean; @@ -19,8 +19,8 @@ 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.Collection; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -31,7 +31,7 @@ import java.util.Optional; * @author huangchengxing */ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class AbstractLockOpsInterceptor +public abstract class AbstractLockInterceptor implements InitializingBean, ApplicationContextAware, Lock4jMethodInterceptor { /** @@ -50,11 +50,6 @@ public abstract class AbstractLockOpsInterceptor @Setter protected ApplicationContext applicationContext; - /** - * 配置 - */ - protected final Lock4jProperties lock4jProperties; - /** * 默认的锁操作配置 */ @@ -74,8 +69,8 @@ public abstract class AbstractLockOpsInterceptor invocation.getMethod(), method -> Optional.ofNullable(resolveLockOps(invocation)).orElse(NULL) ); - return lockOps == NULL ? - invocation.proceed() : doLock(invocation, lockOps); + MethodInvocation invocationWithLock = lockOps.attach(invocation); + return invocationWithLock.proceed(); } @Override @@ -121,55 +116,36 @@ public abstract class AbstractLockOpsInterceptor .flatMap(components -> components.stream().min(AnnotationAwareOrderComparator.INSTANCE)) .map(LockFailureStrategy.class::cast) .orElse(defaultLockOps.getLockFailureStrategy()); - return LockOpsImpl.builder() - .annotation(annotation) - .lockKeyBuilder(keyBuilder) - .lockFailureStrategy(failureStrategy) - .build(); + return new LockOpsImpl(annotation) + .setLockKeyBuilder(keyBuilder) + .setLockFailureStrategy(failureStrategy); } - /** * 初始化默认的锁操作配置 * * @return 默认的锁操作配置 */ @NonNull - protected LockOps initDefaultLockOps() { - LockKeyBuilder lockKeyBuilder = obtainComponent(LockKeyBuilder.class, lock4jProperties.getPrimaryKeyBuilder()); - LockFailureStrategy lockFailureStrategy = obtainComponent(LockFailureStrategy.class, lock4jProperties.getPrimaryFailureStrategy()); - return LockOpsImpl.builder() - .lockKeyBuilder(lockKeyBuilder) - .lockFailureStrategy(lockFailureStrategy) - .build(); - } - - 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")); - } + protected abstract LockOps initDefaultLockOps(); /** - * 进行加锁 + *

获取锁,并执行方法调用。实现类需实现该方法,以实现具体加锁和解锁逻辑。 * - * @param invocation 方法调用 * @param lockOps 锁操作 + * @param invocation 方法调用 * @return 锁信息 * @throws Throwable 调用异常 */ - protected abstract Object doLock(MethodInvocation invocation, LockOps lockOps) 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 { @@ -201,7 +177,17 @@ public abstract class AbstractLockOpsInterceptor */ @Override default int getOrder() { - return Ordered.LOWEST_PRECEDENCE; + return getAnnotation().order(); + } + + /** + * 包装方法调用 + * + * @param invocation 方法调用 + * @return 方法调用 + */ + default MethodInvocation attach(MethodInvocation invocation) { + return invocation; } } @@ -226,10 +212,12 @@ public abstract class AbstractLockOpsInterceptor /** * 默认的锁操作实现 */ + @Accessors(chain = true) @Getter - @SuperBuilder + @Setter + @RequiredArgsConstructor @EqualsAndHashCode(onlyExplicitlyIncluded = true) - protected static class LockOpsImpl implements LockOps { + protected class LockOpsImpl implements LockOps { /** * 锁注解 @@ -250,7 +238,70 @@ public abstract class AbstractLockOpsInterceptor /** * 顺序 */ - @Builder.Default 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/LockAnnotationAdvisor.java b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockAnnotationAdvisor.java index 11b44b4..e474ba9 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,6 +24,7 @@ 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.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -44,7 +45,8 @@ public class LockAnnotationAdvisor extends AbstractPointcutAdvisor implements Be 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 Lock4jMethodInterceptor lockInterceptor, int order) { this.advice = lockInterceptor; 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 index 19a9a86..1a8dfb8 100644 --- a/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java +++ b/lock4j-core/src/main/java/com/baomidou/lock/aop/LockOpsInterceptor.java @@ -1,15 +1,24 @@ 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; /** @@ -18,34 +27,70 @@ import java.util.Objects; * @author huangchengxing */ @Slf4j -public class LockOpsInterceptor extends AbstractLockOpsInterceptor { +public class LockOpsInterceptor extends AbstractConditionalLockChainInterceptor { private final LockTemplate lockTemplate; + protected final Lock4jProperties lock4jProperties; public LockOpsInterceptor( - Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { - super(lock4jProperties); + 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 invocation 方法调用 * @param lockOps 锁操作 + * @param invocation 方法调用 * @return 锁信息 * @throws Throwable 调用异常 */ @Override - protected Object doLock(MethodInvocation invocation, LockOps lockOps) throws Throwable { + 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()); @@ -61,9 +106,11 @@ public class LockOpsInterceptor extends AbstractLockOpsInterceptor { } final boolean releaseLock = lockTemplate.releaseLock(lockInfo); if (!releaseLock) { - log.error("releaseLock fail,lockKey={},lockValue={}", lockInfo.getLockKey(), + 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) { 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 6676c21..bc66169 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,9 +17,9 @@ package com.baomidou.lock.spring.boot.autoconfigure; import com.baomidou.lock.*; -import com.baomidou.lock.aop.ConditionalLockOpsInterceptor; import com.baomidou.lock.aop.Lock4jMethodInterceptor; import com.baomidou.lock.aop.LockAnnotationAdvisor; +import com.baomidou.lock.aop.LockOpsInterceptor; import com.baomidou.lock.executor.LocalLockExecutor; import com.baomidou.lock.executor.LockExecutor; import org.springframework.beans.factory.BeanFactory; @@ -75,9 +75,9 @@ public class LockAutoConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Bean @ConditionalOnMissingBean(Lock4jMethodInterceptor.class) - public ConditionalLockOpsInterceptor conditionalLockOpsInterceptor( + public LockOpsInterceptor conditionalLockOpsInterceptor( Lock4jProperties lock4jProperties, LockTemplate lockTemplate) { - return new ConditionalLockOpsInterceptor(lock4jProperties, lockTemplate); + return new LockOpsInterceptor(lock4jProperties, lockTemplate); } @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 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 0000000..c988733 --- /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 79598f9..ca5e62d 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,11 @@ public class SpringBootLockTest { SpringApplication.run(SpringBootLockTest.class, args); } + @Test + void multiConditionTest() { + userService.multiCondition1(24); + } + @Test void conditionTest() { userService.condition1(-1); 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 3d5d90f..bb0a390 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,8 @@ public interface UserService { void localLock1(long blockTimeMillis); + void multiCondition1(Integer id); + void condition1(Integer id); void simple1(); 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 d155bc2..a0eaaaf 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 @@ -56,10 +56,35 @@ 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) + failStrategy = DefaultLockFailureStrategy.class + ) @Override public void condition1(Integer id) { System.out.println("执行condition1方法 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++)); -- Gitee