From edf969f22fe03a859ccec882bc212bf35f0e85cf Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Fri, 19 Apr 2024 12:37:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=94=81=E5=A4=B1=E8=B4=A5=E6=97=B6=EF=BC=8C=E6=8A=9B?= =?UTF-8?q?=E5=87=BA=E5=B8=A6=E6=9C=89=E6=8C=87=E5=AE=9A=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E7=9A=84=E5=BC=82=E5=B8=B8=20#I8WM77?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lock/AbortLockFailureStrategy.java | 209 ++++++++++++++++++ .../baomidou/lock/LockFailureStrategy.java | 12 +- .../lock/MethodBasedExpressionEvaluator.java | 22 +- .../SpelMethodBasedExpressionEvaluator.java | 18 +- .../lock/annotation/LockWithDefault.java | 123 +++++++++++ .../boot/autoconfigure/Lock4jProperties.java | 7 + .../autoconfigure/LockAutoConfiguration.java | 14 +- .../AbortLockFailureStrategyTest.java | 108 +++++++++ 8 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/AbortLockFailureStrategy.java create mode 100644 lock4j-core/src/main/java/com/baomidou/lock/annotation/LockWithDefault.java create mode 100644 lock4j-core/src/test/java/com/baomidou/lock/executor/AbortLockFailureStrategyTest.java diff --git a/lock4j-core/src/main/java/com/baomidou/lock/AbortLockFailureStrategy.java b/lock4j-core/src/main/java/com/baomidou/lock/AbortLockFailureStrategy.java new file mode 100644 index 0000000..993cbe8 --- /dev/null +++ b/lock4j-core/src/main/java/com/baomidou/lock/AbortLockFailureStrategy.java @@ -0,0 +1,209 @@ +package com.baomidou.lock; + +import com.baomidou.lock.exception.LockFailureException; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.*; + +import java.lang.annotation.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + *
当加锁失败时,抛出异常以终止方法执行。 抛出异常的错误消息。
+ *
+ * 错误信息支持 SpEL 表达式,你可以在表达式中引用上下文参数:
+ * 获取锁失败时,抛出的异常。 抛出异常的错误消息。
+ *
+ * 错误信息支持 SpEL 表达式,你可以在表达式中引用上下文参数:
+ * 获取锁失败时,抛出的异常。
+ * 该策略可以通过{@link Options}注解来指定当失败时要抛出的异常和异常信息。
+ *
+ * @author huangchengxing
+ * @see Options
+ */
+@RequiredArgsConstructor
+public class AbortLockFailureStrategy implements LockFailureStrategy {
+
+ /**
+ * 在SpEL表达式中可通过该参数名引用获取失败的锁键值
+ */
+ private static final String KEY_NAME = "key";
+
+ /**
+ * 默认的错误信息,与{@link DefaultLockFailureStrategy}保持一致
+ */
+ public static final String DEFAULT_EXCEPTION_MESSAGE = "request failed, please retry it.";
+
+ /**
+ * 默认的异常处理器,即直接抛出{@link LockFailureException}异常
+ */
+ private final FailureHandler defaultExceptionFactory = new FailureHandler();
+
+ /**
+ * 异常处理器缓存
+ */
+ private final Map
+ *
+ *
+ * @return 错误消息
+ */
+ String lockFailureMessage() default DEFAULT_EXCEPTION_MESSAGE;
+
+ /**
+ *
+ * 异常类必须提供一个无参构造函数,或者提供一个接受{@code String}类型参数的构造函数。
+ *
+ * @return 异常类型
+ */
+ Class extends Exception> lockFailureException() default LockFailureException.class;
+ }
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/LockFailureStrategy.java b/lock4j-core/src/main/java/com/baomidou/lock/LockFailureStrategy.java
index ffa5e64..e2e5d21 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/LockFailureStrategy.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/LockFailureStrategy.java
@@ -19,13 +19,21 @@ package com.baomidou.lock;
import java.lang.reflect.Method;
/**
+ * 当加锁失败时的处理策略
+ *
* @author zengzhihong
*/
public interface LockFailureStrategy {
/**
- * 锁失败事件
+ * 当加锁失败时的处理策略
+ *
+ * @param key 用于获取锁的key
+ * @param method 方法
+ * @param arguments 方法参数
+ * @throws Exception 处理过程中可能抛出的异常,如果抛出异常则会终止方法执行
*/
- void onLockFailure(String key, Method method, Object[] arguments);
+ void onLockFailure(String key, Method method, Object[] arguments) throws Exception;
+ // TODO 释放锁失败时也应该进行处理,具体参见:https://gitee.com/baomidou/lock4j/issues/I4LG1U
}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/MethodBasedExpressionEvaluator.java b/lock4j-core/src/main/java/com/baomidou/lock/MethodBasedExpressionEvaluator.java
index f919a28..baab147 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/MethodBasedExpressionEvaluator.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/MethodBasedExpressionEvaluator.java
@@ -1,6 +1,10 @@
package com.baomidou.lock;
+import org.springframework.lang.NonNull;
+
import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
/**
* 基于方法调用的表达式计算器
@@ -9,6 +13,19 @@ import java.lang.reflect.Method;
*/
public interface MethodBasedExpressionEvaluator {
+ /**
+ * 执行表达式,返回执行结果
+ *
+ * @param method 方法
+ * @param arguments 调用参数
+ * @param expression 表达式
+ * @param resultType 返回值类型
+ * @param variables 表达式中的变量
+ * @return 表达式执行结果
+ */
+
+ * 过期时间一定是要长于业务的执行时间. 未设置则为默认时间30秒 默认值:{@link Lock4jProperties#expire}
+ *
+ */
+ long expire() default -1;
+
+ /**
+ * @return 获取锁超时时间 单位:毫秒
+ *
+ * 结合业务,建议该时间不宜设置过长,特别在并发高的情况下. 未设置则为默认时间3秒 默认值:{@link Lock4jProperties#acquireTimeout}
+ *
+ */
+ long acquireTimeout() default -1;
+
+ /**
+ * 业务方法执行完后(方法内抛异常也算执行完)自动释放锁,如果为false,锁将不会自动释放直至到达过期时间才释放 {@link com.baomidou.lock.annotation.Lock4j#expire()}
+ *
+ * @return 是否自动释放锁
+ */
+ boolean autoRelease() default true;
+
+ /**
+ * key生成器策略,默认使用{@link DefaultLockKeyBuilder}
+ *
+ * @return LockKeyBuilder
+ */
+ Class extends LockKeyBuilder> keyBuilderStrategy() default DefaultLockKeyBuilder.class;
+
+ /**
+ * 获取顺序,值越小越先执行
+ *
+ * @return 顺序值
+ */
+ int order() default Ordered.LOWEST_PRECEDENCE;
+
+ /**
+ *
+ *
+ *
+ * @return String
+ * @see AbortLockFailureStrategy.Options#lockFailureMessage
+ */
+ @AliasFor(
+ annotation = AbortLockFailureStrategy.Options.class,
+ attribute = "lockFailureMessage"
+ )
+ String lockFailureMessage() default AbortLockFailureStrategy.DEFAULT_EXCEPTION_MESSAGE;
+
+ /**
+ *
+ * 异常类必须提供一个无参构造函数,或者提供一个接受{@code String}类型参数的构造函数。
+ *
+ * @return String
+ * @see AbortLockFailureStrategy.Options#lockFailureException
+ */
+ @AliasFor(
+ annotation = AbortLockFailureStrategy.Options.class,
+ attribute = "lockFailureException"
+ )
+ Class extends Exception> lockFailureException() default LockFailureException.class;
+}
diff --git a/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/Lock4jProperties.java b/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/Lock4jProperties.java
index d7d7db8..4c04e55 100644
--- a/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/Lock4jProperties.java
+++ b/lock4j-core/src/main/java/com/baomidou/lock/spring/boot/autoconfigure/Lock4jProperties.java
@@ -52,10 +52,12 @@ public class Lock4jProperties {
* 默认执行器,不设置默认取容器第一个(默认注入顺序,redisson>redisTemplate>zookeeper)
*/
private Class extends LockExecutor> primaryExecutor;
+
/**
* 默认失败策略,不设置存在多个时默认根据PriorityOrdered、Ordered排序规则选择|注入顺序选择
*/
private Class extends LockFailureStrategy> primaryFailureStrategy;
+
/**
* 默认key生成策略,不设置存在多个时默认根据PriorityOrdered、Ordered排序规则选择|注入顺序选择
*/
@@ -65,4 +67,9 @@ public class Lock4jProperties {
* 锁key前缀
*/
private String lockKeyPrefix = "lock4j";
+
+ /**
+ * 是否允许将非可执行表达式作为字符串
+ */
+ private boolean allowedMakeNonExecutableExpressionsAsString = true;
}
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 314cf51..bab4404 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
@@ -26,6 +26,7 @@ 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.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
@@ -72,9 +73,20 @@ public class LockAutoConfiguration {
}
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ @Primary
@Bean
@ConditionalOnMissingBean
- public LockFailureStrategy lockFailureStrategy() {
+ public AbortLockFailureStrategy abortLockFailureStrategy(
+ MethodBasedExpressionEvaluator methodBasedExpressionEvaluator, Lock4jProperties lock4jProperties) {
+ AbortLockFailureStrategy strategy = new AbortLockFailureStrategy(methodBasedExpressionEvaluator);
+ strategy.setAllowedMakeNonExecutableExpressionsAsString(lock4jProperties.isAllowedMakeNonExecutableExpressionsAsString());
+ return strategy;
+ }
+
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ @Bean
+ @ConditionalOnMissingBean
+ public DefaultLockFailureStrategy defaultLockFailureStrategy() {
return new DefaultLockFailureStrategy();
}
diff --git a/lock4j-core/src/test/java/com/baomidou/lock/executor/AbortLockFailureStrategyTest.java b/lock4j-core/src/test/java/com/baomidou/lock/executor/AbortLockFailureStrategyTest.java
new file mode 100644
index 0000000..a3176c2
--- /dev/null
+++ b/lock4j-core/src/test/java/com/baomidou/lock/executor/AbortLockFailureStrategyTest.java
@@ -0,0 +1,108 @@
+package com.baomidou.lock.executor;
+
+import com.baomidou.lock.AbortLockFailureStrategy;
+import com.baomidou.lock.SpelMethodBasedExpressionEvaluator;
+import com.baomidou.lock.annotation.LockWithDefault;
+import com.baomidou.lock.exception.LockFailureException;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.expression.spel.SpelEvaluationException;
+
+import java.lang.reflect.Method;
+
+/**
+ * test for {@link AbortLockFailureStrategy}
+ *
+ * @author huangchengxing
+ */
+@SpringBootTest(
+ properties = "example.service.message=k3",
+ classes = {SpelMethodBasedExpressionEvaluator.class, AbortLockFailureStrategy.class}
+)
+public class AbortLockFailureStrategyTest {
+
+ @Autowired
+ public AbortLockFailureStrategy abortLockFailureStrategy;
+ private static Method method;
+ private static Method annotatedMethod1;
+ private static Method annotatedMethod2;
+
+ @SneakyThrows
+ @BeforeAll
+ public static void init() {
+ method = AbortLockFailureStrategyTest.class.getDeclaredMethod("method", String.class, String.class);
+ annotatedMethod1 = AbortLockFailureStrategyTest.class.getDeclaredMethod("annotatedMethod1", String.class, String.class);
+ annotatedMethod2 = AbortLockFailureStrategyTest.class.getDeclaredMethod("annotatedMethod2", String.class, String.class);
+ }
+
+ @SneakyThrows
+ @Test
+ void testNonAnnotatedMethod() {
+ Assertions.assertThrows(
+ LockFailureException.class,
+ () -> abortLockFailureStrategy.onLockFailure(
+ "test", method, new Object[]{ "param1", "param2"}
+ ),
+ "request failed, please retry it."
+ );
+ }
+
+ @SneakyThrows
+ @Test
+ void testAnnotatedMethod1() {
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> abortLockFailureStrategy.onLockFailure(
+ "test", annotatedMethod1, new Object[]{ "param1", "param2"}
+ ),
+ "error:param1"
+ );
+ }
+
+ @SneakyThrows
+ @Test
+ void testAnnotatedMethod2() {
+ Assertions.assertThrows(
+ TestException.class,
+ () -> abortLockFailureStrategy.onLockFailure(
+ "test", annotatedMethod2, new Object[]{ "param1", "param2"}
+ ),
+ "error"
+ );
+
+ abortLockFailureStrategy.setAllowedMakeNonExecutableExpressionsAsString(false);
+ Assertions.assertThrows(
+ SpelEvaluationException.class,
+ () -> abortLockFailureStrategy.onLockFailure(
+ "test", annotatedMethod2, new Object[]{ "param1", "param2"}
+ )
+ );
+ abortLockFailureStrategy.setAllowedMakeNonExecutableExpressionsAsString(true);
+ }
+
+ public static class TestException extends RuntimeException {
+ }
+
+ @AbortLockFailureStrategy.Options(
+ lockFailureException = TestException.class,
+ lockFailureMessage = "error"
+ )
+ private void annotatedMethod2(String param1, String param2) {
+ // do nothing
+ }
+
+ @LockWithDefault(
+ lockFailureException = IllegalArgumentException.class,
+ lockFailureMessage = "'error:' + #param1"
+ )
+ private void annotatedMethod1(String param1, String param2) {
+ // do nothing
+ }
+ private void method(String param1, String param2) {
+ // do nothing
+ }
+}
--
Gitee