经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
实现声明式锁,支持分布式锁自定义锁、SpEL和结合事务
来源:cnblogs  作者:是奉壹呀  时间:2023/4/19 9:01:57  对本文有异议

工作中遇到事务一般使用声明式事务,一个注解@Transactional搞定。编程式事务则显得略繁琐。

  1. @Autowired
  2. private PlatformTransactionManager transactionManager;
  3. public void service() throws Exception {
  4. TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
  5. try {
  6. Thread.sleep(1000);
  7. } catch (Exception e) {
  8. transactionManager.rollback(status);
  9. throw e;
  10. }
  11. transactionManager.commit(status);
  12. }

但是遇到需要加锁的情况呢?
绝大多数情况都是使用编程式锁。例如:

  1. ReentrantLock lock = new ReentrantLock();
  2. public void service() throws Exception {
  3. try {
  4. lock.lock();
  5. Thread.sleep(1000);
  6. } finally {
  7. lock.unlock();
  8. }
  9. }

但为什么不搞个轮子把编程式锁变为声明式锁呢?
本文尝试着写一个,欢迎大家指正。


2.实现

众所周知,声明式事务使用注解+AOP,同理声明式锁也应该一样。那么先搞一个注解。

2.1 定义注解

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. @Documented
  5. public @interface Locked {
  6. /**
  7. * 锁名称,如果不传会默认使用同一把锁
  8. * 支持SpEL表达式
  9. * @return
  10. */
  11. String key() default "";
  12. /**
  13. * 锁类型
  14. * @return
  15. */
  16. LockType lockType() default LockType.ReentrantLock;
  17. /**
  18. * 获取锁的时间,超出时间没有获取到锁返回false
  19. * 默认为0
  20. * -1 为永不超时,这种情况下,tryLock()会一直阻塞到获取锁
  21. * @return
  22. */
  23. long time() default 0;
  24. /**
  25. * 获取锁的时间单位
  26. * 默认为秒
  27. * @return
  28. */
  29. TimeUnit timeUnit() default TimeUnit.SECONDS;
  30. }

再定义一个枚举类型的锁类型

  1. public enum LockType {
  2. ReentrantLock,
  3. RedissonClient,
  4. ELSE
  5. }

2.2 定义锁接口

然后仿照java.util.concurrent.locks.Lock定义一个锁的接口。

  1. public interface LockedService {
  2. boolean lock();
  3. boolean tryLock();
  4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  5. void unLock();
  6. /**
  7. * 如果是redisson分布式锁,调用方通过此方法将相应的redissonClient对象和key传到具体实现类
  8. * @param redissonClient
  9. * @param key
  10. * @return
  11. */
  12. LockedService setRedissonClient(RedissonClient redissonClient, String key);
  13. /**
  14. * 如果是ReentrantLock,调用方通过此方法将相应的lock对象传到具体实现类
  15. * @param lock
  16. * @return
  17. */
  18. LockedService setLock(Lock lock);
  19. }

主要是加锁解锁的动作。加锁分为阻塞与非阻塞,有时间参数与非时间参数之分。
与jdk的 lock接口完全相似。
里面多余的方法分别为:

  1. setRedissonClient()
    如果是redisson分布式锁,调用方通过此方法将相应的redissonClient对象和key传到具体实现类
  2. setLock()
    如果是ReentrantLock,调用方通过此方法将相应的lock对象传到具体实现类

如果是自定义锁,在自定义的实现类里面可以忽略这两个方法,自定义获取锁对象。

2.3 锁的实现

锁的接口至少需要两个实现类,一个是ReentrantLock,另一个是RedissonClient
如果是直接定义两个类,

  1. @Component
  2. public class LockedServiceImpl implements LockedService{}
  3. @Component
  4. public class LockedRedissonServiceImpl implements LockedService{}

然后在切面里直接使用

  1. @Autowired
  2. private LockedService lockedService;

启动就会报错:

  1. Field lockedService in com.nyp.test.service.LockAspect required a single bean, but 2 were found:
  2. - lockedRedissonServiceImpl: defined in file [\target\classes\com\nyp\test\service\LockedRedissonServiceImpl.class]
  3. - lockedServiceImpl: defined in file [\target\classes\com\nyp\test\service\LockedServiceImpl.class]

就算通过@Primary或者@Qualifier将这两个实现类都注入了进来,也不好分辨究竟使用哪一个。

这个使用就需要用到SPI了。

2.3.1 什么是SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

有点类似于接口+策略模式+配置文件组合实现的动态加载机制。

比如spring mvc/springboot里面有个HttpMessageConverters,HTTP的request和response的转换器。当一个请求完成时,返回一个对象,需要它将对象转换成json串(或其它),然后以流的形式写到客户端。这个的工具有很多,比如jackson,gson等,spring默认采用jackson框架,AbstractJackson2HttpMessageConverter.

也很多同学实际上使用的是fastjson,FastJsonHttpMessageConverter。或者其它。
image

不管使用哪一种框架,它都要去实现HttpMessageConverters接口。但springboot容器加载的时候怎么知道需要去加载哪些实现类,具体又使用哪个实现类呢。

这里就使用到了SPI机制。

2.3.2 通过SPI实现锁的多个实现类

SPI有jdk的实现,有spring boot的实现。这里使用springboot的实现方法。

resources/META-INF目录下新装置文件 spring.factories,内容为锁接口及其实现类的全限定类名。

  1. com.nyp.test.service.LockedService=com.nyp.test.service.LockedServiceImpl,com.nyp.test.service.LockedRedissonServiceImpl

同时在类LockedServiceImplLockedRedissonServiceImpl就不需要加@Component注解。
LockedServiceImpl类:

  1. import org.redisson.api.RedissonClient;
  2. import org.springframework.stereotype.Component;
  3. import java.util.concurrent.TimeUnit;
  4. import java.util.concurrent.locks.Lock;
  5. /**
  6. * @projectName: Test
  7. * @package: com.nyp.test.service
  8. * @className: com.nyp.test.service.LockedService
  9. * @author: nyp
  10. * @description: jdk ReentrantLock锁实现
  11. * @date: 2023/4/13 11:45
  12. * @version: 1.0
  13. */
  14. @Component
  15. public class LockedServiceImpl implements LockedService{
  16. private Lock lock;
  17. @Override
  18. public boolean lock(){
  19. if (lock != null) {
  20. lock.lock();
  21. return true;
  22. }
  23. return false;
  24. }
  25. @Override
  26. public boolean tryLock() {
  27. return lock.tryLock();
  28. }
  29. @Override
  30. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  31. return lock.tryLock(time, unit);
  32. }
  33. @Override
  34. public void unLock(){
  35. if (lock != null) {
  36. lock.unlock();
  37. }
  38. }
  39. @Override
  40. public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
  41. return this;
  42. }
  43. @Override
  44. public LockedService setLock(Lock lock) {
  45. this.lock = lock;
  46. return this;
  47. }
  48. }

LockedRedissonServiceImpl类:

  1. import org.redisson.api.RLock;
  2. import org.redisson.api.RedissonClient;
  3. import java.util.concurrent.TimeUnit;
  4. import java.util.concurrent.locks.Lock;
  5. /**
  6. * @projectName: Test
  7. * @package: com.nyp.test.service
  8. * @className: com.nyp.test.service.LockedService
  9. * @author: nyp
  10. * @description: redisson分布式锁实现
  11. * @date: 2023/4/13 11:45
  12. * @version: 1.0
  13. */
  14. public class LockedRedissonServiceImpl implements LockedService {
  15. private RedissonClient redissonClient;
  16. private String key;
  17. @Override
  18. public boolean lock() {
  19. RLock rLock = redissonClient.getLock(key);
  20. if (rLock != null) {
  21. rLock.lock();
  22. return true;
  23. }
  24. return false;
  25. }
  26. @Override
  27. public boolean tryLock() {
  28. RLock rLock = redissonClient.getLock(key);
  29. return rLock.tryLock();
  30. }
  31. @Override
  32. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  33. RLock rLock = redissonClient.getLock(key);
  34. return rLock.tryLock(time, unit);
  35. }
  36. @Override
  37. public void unLock() {
  38. RLock rLock = redissonClient.getLock(key);
  39. if (rLock != null && rLock.isHeldByCurrentThread()) {
  40. rLock.unlock();
  41. }
  42. }
  43. @Override
  44. public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
  45. this.redissonClient = redissonClient;
  46. this.key = key;
  47. return this;
  48. }
  49. @Override
  50. public LockedService setLock(Lock lock) {
  51. return this;
  52. }
  53. }

那么spring容器里面就有两个锁接口实现了,到底使用哪一个呢?
模仿HttpMessageConverters搞一个转换器。在init()方法里面指定默认使用com.nyp.test.service.LockedServiceImpl.

  1. public class LockedServiceConverters {
  2. private LockedService lockedService;
  3. private String lockedServiceImplClass;
  4. public LockedServiceConverters(String lockedServiceImplClass) {
  5. this.lockedServiceImplClass = lockedServiceImplClass;
  6. }
  7. public LockedService getLockedService() {
  8. return lockedService;
  9. }
  10. @PostConstruct
  11. public void init() throws Exception {
  12. if (StringUtils.isBlank(lockedServiceImplClass)) {
  13. lockedServiceImplClass = "com.nyp.test.service.LockedServiceImpl";
  14. }
  15. List<LockedService> lockedServiceList = SpringFactoriesLoader.loadFactories(LockedService.class, null);
  16. for (LockedService lockedService : lockedServiceList){
  17. System.out.println(lockedService.getClass().getName());
  18. if(lockedService.getClass().getName().equals(lockedServiceImplClass)){
  19. this.lockedService = lockedService;
  20. }
  21. }
  22. if (lockedService == null) {
  23. throw new Exception("未发现lockedService : " + lockedServiceImplClass);
  24. }
  25. }
  26. }

使用的时候可以通过以下方式指定使用LockedRedissonServiceImpl,或其它自定义的锁实现类。

  1. @Configuration
  2. public class WebConfig {
  3. @Bean
  4. public LockedServiceConverters lockedServiceConverters() {
  5. return new LockedServiceConverters("com.nyp.test.service.LockedRedissonServiceImpl");
  6. }
  7. }

2.3.3 通过SPI自定义实现锁

首先实现一个LockedService接口。

  1. public class LockedServiceUserDefine implements LockedService{
  2. @Override
  3. public boolean lock() {
  4. log.info("锁住");
  5. return true;
  6. }
  7. @Override
  8. public boolean tryLock() {
  9. return true;
  10. }
  11. @Override
  12. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  13. return true;
  14. }
  15. @Override
  16. public void unLock() {
  17. log.info("解锁");
  18. }
  19. @Override
  20. public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
  21. return this;
  22. }
  23. @Override
  24. public LockedService setLock(Lock lock) {
  25. return this;
  26. }
  27. }

然后将此类加到spring.factory里面去

  1. com.nyp.test.service.LockedService=com.nyp.test.service.LockedServiceImpl,com.nyp.test.service.LockedRedissonServiceImpl,com.nyp.test.service.LockedServiceUserDefine

再将LockedServiceConverters改为LockedServiceUserDefine即可。

  1. @Bean
  2. public LockedServiceConverters lockedServiceConverters() {
  3. return new LockedServiceConverters("com.nyp.test.service.LockedServiceUserDefine");
  4. }

3.定义切面

3.1 切面实现

  1. import com.nyp.test.config.LockType;
  2. import com.nyp.test.config.Locked;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.*;
  7. import org.aspectj.lang.reflect.MethodSignature;
  8. import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
  9. import org.redisson.api.RedissonClient;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.core.DefaultParameterNameDiscoverer;
  12. import org.springframework.expression.EvaluationContext;
  13. import org.springframework.expression.Expression;
  14. import org.springframework.expression.spel.standard.SpelExpressionParser;
  15. import org.springframework.expression.spel.support.StandardEvaluationContext;
  16. import org.springframework.stereotype.Component;
  17. import org.springframework.transaction.support.TransactionSynchronization;
  18. import org.springframework.transaction.support.TransactionSynchronizationManager;
  19. import java.util.concurrent.locks.Lock;
  20. import java.util.concurrent.locks.ReentrantLock;
  21. /**
  22. * @projectName: Test
  23. * @package: com.nyp.test.service
  24. * @className: LockAspect
  25. * @author: nyp
  26. * @description: TODO
  27. * @date: 2023/4/13 10:46
  28. * @version: 1.0
  29. */
  30. @Aspect
  31. @Component
  32. @Slf4j
  33. public class LockAspect {
  34. /**
  35. * 用于保存ReentrantLock类的锁
  36. */
  37. private volatile ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();
  38. @Autowired
  39. private LockedServiceConverters lockedServiceConverters;
  40. @Autowired
  41. private RedissonClient redissonClient;
  42. /**
  43. * 用于SpEL表达式解析.
  44. */
  45. private SpelExpressionParser parser = new SpelExpressionParser();
  46. /**
  47. * 用于获取方法参数定义名字.
  48. */
  49. private DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
  50. @Around("@annotation(locked)")
  51. public Object around(ProceedingJoinPoint joinPoint, Locked locked) throws Throwable {
  52. String lockKey = getKeyBySpEL(locked.key(), joinPoint);
  53. if (StringUtils.isBlank(lockKey)) {
  54. lockKey = joinPoint.getTarget().toString();
  55. log.info("lockKey = {}", lockKey);
  56. }
  57. Lock lock = locks.get(lockKey);
  58. lock = getLockFromLocks(lock, locked.lockType(), lockKey);
  59. boolean isLock;
  60. if (locked.time() != 0) {
  61. if (locked.time() < 0) {
  62. isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).tryLock();
  63. } else {
  64. isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).tryLock(locked.time(), locked.timeUnit());
  65. }
  66. } else {
  67. isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).lock();
  68. }
  69. Object obj;
  70. try {
  71. obj = joinPoint.proceed();
  72. } catch (Throwable throwable) {
  73. throw throwable;
  74. } finally {
  75. if(isLock){
  76. // 如果有事务,保证事务完成(commit or rollback)过后再释放锁
  77. // 这里不管声明式事务,还是编程式事务,只要还没完成就会进入
  78. // TODO 这里要考虑到事务的传播,特别是嵌套事务
  79. if(TransactionSynchronizationManager.isActualTransactionActive()){
  80. Lock finalLock = lock;
  81. String finalLockKey = lockKey;
  82. TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  83. @Override
  84. public void afterCompletion(int status) {
  85. lockedServiceConverters.getLockedService().setLock(finalLock).setRedissonClient(redissonClient, finalLockKey).unLock();
  86. }
  87. });
  88. } else{
  89. lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).unLock();
  90. }
  91. }
  92. }
  93. return obj;
  94. }
  95. private Lock getLockFromLocks(Lock lock, LockType lockType, String lockKey) {
  96. // TODO 非Lock类锁可以不调用此方法
  97. if (lock == null) {
  98. synchronized (LockAspect.class){
  99. lock = locks.get(lockKey);
  100. if (lock == null) {
  101. if (lockType == LockType.ReentrantLock) {
  102. lock = new ReentrantLock();
  103. locks.put(lockKey, lock);
  104. }
  105. }
  106. }
  107. }
  108. return lock;
  109. }
  110. public String getKeyBySpEL(String expressionStr, ProceedingJoinPoint joinPoint) {
  111. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  112. String[] paramNames = discoverer.getParameterNames(methodSignature.getMethod());
  113. Expression expression = parser.parseExpression(expressionStr);
  114. EvaluationContext context = new StandardEvaluationContext();
  115. Object[] args = joinPoint.getArgs();
  116. for (int i = 0; i < args.length; i++) {
  117. context.setVariable(paramNames[i], args[i]);
  118. }
  119. return expression.getValue(context) == null ? "" : expression.getValue(context).toString();
  120. }
  121. }

1.其中locks用于保存ReentrantLock类的锁,每个key一个锁对象,如果key为空,默认以方法名为key,意味着同一个方法共用一个锁对象。

2.lockedServiceConverters为锁转换器,通过它可以获取容器当中使用的真正锁对象。
3.redissonClient对象在这里注入,通过lockedServiceConverters获取的锁实现类将此锁对象注入到实现方法内部。
4.SpelExpressionParserDefaultParameterNameDiscoverer对象用于通过SpEL表达式动态从目标方法参数中获取锁的key。

5.@Around("@annotation(locked)")这里使用一个环绕通知,拦截加了locked注解的方法,进行锁操作。

具体执行流程为,拦截到方法:
1.先获取到lockkey,然后根据lockkey获取锁对象。
2.根据lockedServiceConverters拿到具体的锁实现类对象,根据锁对象类型以及time等参数,将需要的redissonClient,lock等参数传入,再调用锁方法实现,进行加锁操作。
3.调用目标方法。
4.目标方法调用完毕,进行解锁操作。这里判断一下是否还在一个事务当中,如果是的话,在事务完成之后再进行解锁。这块在后面再详细说明。

3.2 SpEL表达式获取动态key

定义一个测试目标方法。key为#person.name

  1. @Transactional(rollbackFor = Exception.class)
  2. @Locked(key = "#person.name", lockType = LockType.RedissonClient, time = 10, timeUnit = TimeUnit.MILLISECONDS)
  3. public void service(Person person) throws Exception {
  4. Thread.sleep(1000);
  5. log.info("业务方法");
  6. }

再调用

  1. Person person = new Person();
  2. person.setName("张三");
  3. lockTest.service(person);

准确获取到了张三
image

3.3 锁与事务的结合

锁与事务结合为什么要单独拿出来讲?有什么问题吗?

具体可以看我的另一篇博文 https://juejin.cn/post/7213636024112234551 当transcational遇上synchronized。
不管是synchronized还是Lock。道理是一样的。

简单说,spring使用动态代理加AOP实现事务管理。那么上面的方法实际上至少需要简化成3个步骤:

  1. void begin();
  2. @Transactional(rollbackFor = Exception.class)
  3. public void service(Person person) throws Exception {
  4. try{
  5. lock.lock();
  6. } finally{
  7. lock.unlock();
  8. }
  9. }
  10. void commit();
  11. // void rollback();

如果在service()中向数据库insert/update一条数据,在serive()执行完毕锁释放之后,commit之前,有另一个线程拿到了锁开始执行service()方法,那么这时候它是读不到数据库里最新的记录的。除非事务隔离级别为读未提交。

但实际生产环境中,少有人使用读未提交这种隔离级别,为了避免上述的线程安全问题,就得借助事务同步器TransactionSynchronization来实现。当线程中存在未完成的事务时,需要在afterCompletion方法里释放锁。afterCompletion表示事务完成,包括提交与回滚。

image

4.测试


4.1 ReentrantLock测试

  1. @Bean
  2. public LockedServiceConverters lockedServiceConverters() {
  3. return new LockedServiceConverters("com.nyp.test.service.LockedServiceImpl");
  4. }

业务方法阻塞,锁获取超时时间为60秒。

  1. @Transactional(rollbackFor = Exception.class)
  2. @Locked(key = "#person.name", lockType = LockType.ReentrantLock, time = 60000, timeUnit = TimeUnit.MILLISECONDS)
  3. public void service(Person person) throws Exception {
  4. Thread.sleep(10000000);
  5. log.info("业务方法");
  6. }

先测试张三锁,再测试李四锁,再获取李四锁
预期结果是张三获取到锁,李四因为是新的key所以也能获取锁,李四再次获取锁,因为之前李四获取的锁还没释放,所以一直阻塞获取不了锁。

执行日志:

  1. [2023-04-18 19:02:17.639] INFO 53268 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : key = 张三
  2. [2023-04-18 19:02:17.640] INFO 53268 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : 张三 尝试获取锁
  3. [2023-04-18 19:02:17.641] INFO 53268 [http-nio-8657-exec-1] [com.nyp.test.service.LockedServiceImpl] : 张三 获取到了锁
  4. [2023-04-18 19:02:20.280] INFO 53268 [http-nio-8657-exec-4] [com.nyp.test.service.LockAspect] : key = 李四
  5. [2023-04-18 19:02:20.281] INFO 53268 [http-nio-8657-exec-4] [com.nyp.test.service.LockAspect] : 李四 尝试获取锁
  6. [2023-04-18 19:02:20.281] INFO 53268 [http-nio-8657-exec-4] [com.nyp.test.service.LockedServiceImpl] : 李四 获取到了锁
  7. [2023-04-18 19:02:22.181] INFO 53268 [http-nio-8657-exec-3] [com.nyp.test.service.LockAspect] : key = 李四
  8. [2023-04-18 19:02:22.181] INFO 53268 [http-nio-8657-exec-3] [com.nyp.test.service.LockAspect] : 李四 尝试获取锁
  9. [2023-04-18 19:03:22.186] INFO 53268 [http-nio-8657-exec-3] [com.nyp.test.service.LockedServiceImpl] : 李四 获取锁超时

执行日志符合预期,李四第2次尝试获取锁,在60秒后超时失败。

4.2 RedissonClient测试


  1. @Bean
  2. public LockedServiceConverters lockedServiceConverters() {
  3. return new LockedServiceConverters("com.nyp.test.service.LockedRedissonServiceImpl");
  4. }

还是上面的测试代码,结果符合预期。

  1. [2023-04-18 19:10:47.895] INFO 117888 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : key = 张三
  2. [2023-04-18 19:10:47.896] INFO 117888 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : 张三 尝试获取锁
  3. [2023-04-18 19:10:47.904] INFO 117888 [http-nio-8657-exec-1] [com.nyp.test.service.LockedRedissonServiceImpl] : 张三 获取到了锁
  4. [2023-04-18 19:10:50.555] INFO 117888 [http-nio-8657-exec-5] [com.nyp.test.service.LockAspect] : key = 李四
  5. [2023-04-18 19:10:50.556] INFO 117888 [http-nio-8657-exec-5] [com.nyp.test.service.LockAspect] : 李四 尝试获取锁
  6. [2023-04-18 19:10:50.557] INFO 117888 [http-nio-8657-exec-5] [com.nyp.test.service.LockedRedissonServiceImpl] : 李四 获取到了锁
  7. [2023-04-18 19:10:55.882] INFO 117888 [http-nio-8657-exec-8] [com.nyp.test.service.LockAspect] : key = 李四
  8. [2023-04-18 19:10:55.883] INFO 117888 [http-nio-8657-exec-8] [com.nyp.test.service.LockAspect] : 李四 尝试获取锁
  9. [2023-04-18 19:11:55.896] INFO 117888 [http-nio-8657-exec-8] [com.nyp.test.service.LockedRedissonServiceImpl] : 李四 获取锁超时

4.3 自定义锁测试

使用LockedServiceUserDefine

  1. @Bean
  2. public LockedServiceConverters lockedServiceConverters() {
  3. return new LockedServiceConverters("com.nyp.test.service.LockedServiceUserDefine");
  4. }
  1. @Transactional(rollbackFor = Exception.class)
  2. @Locked(key = "#person.name", lockType = LockType.ELSE, time = 60000, timeUnit = TimeUnit.MILLISECONDS)
  3. public void service(Person person) throws Exception {
  4. log.info("业务方法");
  5. }

日志:

  1. [2023-04-18 19:19:38.897] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : key = 张三
  2. [2023-04-18 19:19:38.898] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : 张三 尝试获取锁
  3. [2023-04-18 19:19:38.898] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockedServiceUserDefine] : 锁住
  4. [2023-04-18 19:19:38.900] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockTest] : 业务方法
  5. [2023-04-18 19:19:38.901] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockedServiceUserDefine] : 解锁

这里只是证明容器执行了自定义的实现类。没有真正实现锁。

5.尾声


5.1 todo list

1.将locks里不用的锁定期清除。
2.getLockFromLocks()方法非Lock类的锁可不执行。
3.重要:在切面里,在事务同步器afterCompletion后再释放锁,这里要考虑到事务的传播性,特别是嵌套事务的情况。这种情况下,会把锁的范围扩大。

5.2 小结

在这里我们实现了一个声明式的锁,在注解里支持锁的粒度(key和SpEL动态key),支持定义获取锁的超时时间,默认支持ReentrantLockRedissonClient两种锁。

也使用SPI支持自定义锁实现。

支持锁与事务的整合(考虑到事务的传播,或者叫有限支持)。


但是!!没经过严格的测试,特别是并发测试,不建议在生产环境中使用,仅作学习交流之用。
希望能够对各位看官有所启发,欢迎留言讨论。

原文链接:https://www.cnblogs.com/eryuan/p/17328694.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号