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 0000000000000000000000000000000000000000..993cbe86c1415f0a0a7abc24a75cbc0ddb607b5a --- /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 ffa5e64d1b64eabba43519e80d60b14d9b846ce0..e2e5d21bd2de3dac0d68756402fa4d70189ce079 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 f919a28ac9bca9d381ee8b25a375971d1a216229..baab14701b3229662c649081668fda3130f475bb 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 d7d7db8c69275f7fdbe5aca98d1b18157ce09e4a..4c04e55c87e6ed950cbc8883b6a5bb1a65f4901f 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 314cf510db2e3e4bad779de7a539cdced8d3304e..bab44048e254cbfec3c24bd10b295935737fee32 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 0000000000000000000000000000000000000000..a3176c28ddf8727cd1913b53e02bcf1d43d733c8
--- /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
+ }
+}