实现声明式锁,支持分布式锁自定义锁、SpEL和结合事务

news2025/3/13 5:26:30

目录

  • 2.实现
    • 2.1 定义注解
    • 2.2 定义锁接口
    • 2.3 锁的实现
      • 2.3.1 什么是SPI
      • 2.3.2 通过SPI实现锁的多个实现类
      • 2.3.3 通过SPI自定义实现锁
  • 3.定义切面
    • 3.1 切面实现
    • 3.2 SpEL表达式获取动态key
    • 3.3 锁与事务的结合
  • 4.测试
    • 4.1 ReentrantLock测试
    • 4.2 RedissonClient测试
    • 4.3 自定义锁测试
  • 5.尾声
    • 5.1 todo list
    • 5.2 小结

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

	@Autowired
    private PlatformTransactionManager transactionManager;

    public void service() throws Exception {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
        transactionManager.commit(status);
    }

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

	ReentrantLock lock = new ReentrantLock();
    public void service() throws Exception {
        try {
            lock.lock();
            Thread.sleep(1000);
        } finally {
            lock.unlock();
        }
    }

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

2.实现

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

2.1 定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Locked {

    /**
     * 锁名称,如果不传会默认使用同一把锁
     * 支持SpEL表达式
     * @return
     */
    String key() default "";


    /**
     * 锁类型
     * @return
     */
    LockType lockType() default LockType.ReentrantLock;

    /**
     * 获取锁的时间,超出时间没有获取到锁返回false
     * 默认为0
     * -1 为永不超时,这种情况下,tryLock()会一直阻塞到获取锁
     * @return
     */
    long time() default 0;

    /**
     * 获取锁的时间单位
     * 默认为秒
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

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

public enum LockType {
     ReentrantLock,
     RedissonClient,
     ELSE
}

  •  1. key为锁ID。对于分布式锁来说很好理解。对于jdk层面的锁Lock来说,会将锁存放到map对象,这里的锁ID即为map的key。相同的key共享同一个锁对象,不同的key第一次执行时需新建锁对象。
    这一点在需要降低锁粒度的时候有用。

  •  2.lockType为锁的类型,这里实现了两种,默认为ReentrantLock,另外实现了redisson的分布式锁。还会预留接口,可以自定义其它的锁实现。

  •  3.time为获取锁的超时时间。默认为0,此时将使用lock()获取锁。如果大于0将使用tryLock(time, timeunit)获取锁。如果为-1则表示不超时,使用tryLock()阻塞至获取到锁。

  •  4.timeUnit为时间单位,默认为秒。

2.2 定义锁接口

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

public interface LockedService {
   boolean lock();

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unLock();

    /**
     * 如果是redisson分布式锁,调用方通过此方法将相应的redissonClient对象和key传到具体实现类
     * @param redissonClient
     * @param key
     * @return
     */
    LockedService setRedissonClient(RedissonClient redissonClient, String key);

    /**
     * 如果是ReentrantLock,调用方通过此方法将相应的lock对象传到具体实现类
     * @param lock
     * @return
     */
    LockedService setLock(Lock lock);
}

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

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

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

2.3 锁的实现

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

@Component
public class LockedServiceImpl implements LockedService{}
@Component
public class LockedRedissonServiceImpl implements LockedService{}

然后在切面里直接使用

 @Autowired
 private LockedService lockedService;

启动就会报错:

Field lockedService in com.nyp.test.service.LockAspect required a single bean, but 2 were found:
	- lockedRedissonServiceImpl: defined in file [\target\classes\com\nyp\test\service\LockedRedissonServiceImpl.class]
	- 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。或者其它。

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

这里就使用到了SPI机制。

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

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

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

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

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

import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * @projectName: Test
 * @package: com.nyp.test.service
 * @className: com.nyp.test.service.LockedService
 * @author: nyp
 * @description: jdk ReentrantLock锁实现
 * @date: 2023/4/13 11:45
 * @version: 1.0
 */
@Component
public class LockedServiceImpl implements LockedService{

    private Lock lock;

    @Override
    public boolean lock(){
        if (lock != null) {
            lock.lock();
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock() {
        return lock.tryLock();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return lock.tryLock(time, unit);
    }

    @Override
    public void unLock(){
        if (lock != null) {
            lock.unlock();
        }
    }

    @Override
    public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
        return this;
    }

    @Override
    public LockedService setLock(Lock lock) {
        this.lock = lock;
        return this;
    }

}

LockedRedissonServiceImpl类:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * @projectName: Test
 * @package: com.nyp.test.service
 * @className: com.nyp.test.service.LockedService
 * @author: nyp
 * @description: redisson分布式锁实现
 * @date: 2023/4/13 11:45
 * @version: 1.0
 */
public class LockedRedissonServiceImpl implements LockedService {
    private RedissonClient redissonClient;
    private String key;

    @Override
    public boolean lock() {
        RLock rLock = redissonClient.getLock(key);
        if (rLock != null) {
            rLock.lock();
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock() {
        RLock rLock = redissonClient.getLock(key);
        return rLock.tryLock();
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        RLock rLock = redissonClient.getLock(key);
        return rLock.tryLock(time, unit);
    }

    @Override
    public void unLock() {
        RLock rLock = redissonClient.getLock(key);
        if (rLock != null && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }

    @Override
    public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
        this.redissonClient = redissonClient;
        this.key = key;
        return this;
    }

    @Override
    public LockedService setLock(Lock lock) {
        return this;
    }
}

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

public class LockedServiceConverters {
    private LockedService lockedService;
    private String lockedServiceImplClass;

    public LockedServiceConverters(String lockedServiceImplClass) {
        this.lockedServiceImplClass = lockedServiceImplClass;
    }

    public LockedService getLockedService() {
        return lockedService;
    }

    @PostConstruct
    public void init() throws Exception {
        if (StringUtils.isBlank(lockedServiceImplClass)) {
            lockedServiceImplClass = "com.nyp.test.service.LockedServiceImpl";
        }

        List<LockedService> lockedServiceList = SpringFactoriesLoader.loadFactories(LockedService.class, null);
        for (LockedService lockedService : lockedServiceList){
            System.out.println(lockedService.getClass().getName());
            if(lockedService.getClass().getName().equals(lockedServiceImplClass)){
                this.lockedService = lockedService;
            }
        }
        if (lockedService == null) {
            throw new Exception("未发现lockedService : " + lockedServiceImplClass);
        }
    }
}

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

@Configuration
public class WebConfig {
    @Bean
    public LockedServiceConverters lockedServiceConverters() {
        return new LockedServiceConverters("com.nyp.test.service.LockedRedissonServiceImpl");
    }
}

2.3.3 通过SPI自定义实现锁

首先实现一个LockedService接口。

public class LockedServiceUserDefine implements LockedService{
    @Override
    public boolean lock() {
        log.info("锁住");
        return true;
    }

    @Override
    public boolean tryLock() {
        return true;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return true;
    }

    @Override
    public void unLock() {
        log.info("解锁");
    }

    @Override
    public LockedService setRedissonClient(RedissonClient redissonClient, String key) {
        return this;
    }

    @Override
    public LockedService setLock(Lock lock) {
        return this;
    }
}

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

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

再将LockedServiceConverters改为LockedServiceUserDefine即可。

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

3.定义切面

3.1 切面实现

import com.nyp.test.config.LockType;
import com.nyp.test.config.Locked;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @projectName: Test
 * @package: com.nyp.test.service
 * @className: LockAspect
 * @author: nyp
 * @description: TODO
 * @date: 2023/4/13 10:46
 * @version: 1.0
 */
@Aspect
@Component
@Slf4j
public class LockAspect {

    /**
     * 用于保存ReentrantLock类的锁
     */
    private volatile ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();

    @Autowired
    private LockedServiceConverters lockedServiceConverters;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 用于SpEL表达式解析.
     */
    private SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(locked)")
    public Object around(ProceedingJoinPoint joinPoint, Locked locked) throws Throwable {
        String lockKey = getKeyBySpEL(locked.key(), joinPoint);
        if (StringUtils.isBlank(lockKey)) {
            lockKey = joinPoint.getTarget().toString();
            log.info("lockKey = {}", lockKey);
        }
        Lock lock = locks.get(lockKey);
        lock = getLockFromLocks(lock, locked.lockType(), lockKey);
        boolean isLock;
        if (locked.time() != 0) {
            if (locked.time() < 0) {
                isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).tryLock();
            } else {
                isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).tryLock(locked.time(), locked.timeUnit());
            }
        } else {
            isLock = lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).lock();
        }
        Object obj;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throw  throwable;
        } finally {
            if(isLock){
                // 如果有事务,保证事务完成(commit or rollback)过后再释放锁
                // 这里不管声明式事务,还是编程式事务,只要还没完成就会进入
				// TODO 这里要考虑到事务的传播,特别是嵌套事务
                if(TransactionSynchronizationManager.isActualTransactionActive()){
                    Lock finalLock = lock;
                    String finalLockKey = lockKey;
                    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                        @Override
                        public void afterCompletion(int status) {
                            lockedServiceConverters.getLockedService().setLock(finalLock).setRedissonClient(redissonClient, finalLockKey).unLock();
                        }
                    });
                } else{
                    lockedServiceConverters.getLockedService().setLock(lock).setRedissonClient(redissonClient, lockKey).unLock();
                }
            }
        }
        return obj;
    }

    private Lock getLockFromLocks(Lock lock, LockType lockType, String lockKey) {
        // TODO 非Lock类锁可以不调用此方法
        if (lock == null) {
            synchronized (LockAspect.class){
                lock = locks.get(lockKey);
                if (lock == null) {
                    if (lockType == LockType.ReentrantLock) {
                        lock = new ReentrantLock();
                        locks.put(lockKey, lock);
                    }
                }
            }
        }
        return lock;
    }

    public String getKeyBySpEL(String expressionStr, ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = discoverer.getParameterNames(methodSignature.getMethod());
        Expression expression = parser.parseExpression(expressionStr);
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context) == null ? "" : expression.getValue(context).toString();
    }

}

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

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

再调用

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

准确获取到了张三

3.3 锁与事务的结合

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

具体可以看我的另一篇博文 当transcational遇上synchronized - 掘金 当transcational遇上synchronized。
不管是synchronized还是Lock。道理是一样的。

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

void begin();

@Transactional(rollbackFor = Exception.class)
public void service(Person person) throws Exception {
  try{
  	lock.lock();
  } finally{
  	lock.unlock();
  }
}

void commit();
// void rollback();

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

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

4.测试

4.1 ReentrantLock测试

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

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

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

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

执行日志:

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

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

4.2 RedissonClient测试

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

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

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

4.3 自定义锁测试

使用LockedServiceUserDefine

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

日志:

[2023-04-18 19:19:38.897] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : key = 张三
[2023-04-18 19:19:38.898] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockAspect] : 张三 尝试获取锁 
[2023-04-18 19:19:38.898] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockedServiceUserDefine] : 锁住
[2023-04-18 19:19:38.900] INFO 6264 [http-nio-8657-exec-1] [com.nyp.test.service.LockTest] : 业务方法
[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支持自定义锁实现。

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

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

苍茫之天涯,乃吾辈之所爱也;浩瀚之程序,亦吾之所爱也,然则何时而爱耶?必曰:先天下之忧而忧,后天下之爱而爱也!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

移动硬盘如何分区?教您快速解决!

案例&#xff1a;怎么对移动硬盘进行分区&#xff1f; 【我平常找一个文件需要耗费很长时间&#xff0c;十分麻烦。我现在想通过对移动硬盘进行分区的方式&#xff0c;整理好我的文件&#xff0c;方便使用时查找。有没有人知道移动硬盘怎么分区&#xff1f;教教我&#xff01;…

深入浅出JS定时器:从setTimeout到setInterval

前言 当谈到 JavaScript 编程语言最基本的概念时&#xff0c;定时器就是一个必须掌握的知识点。在编写网站时&#xff0c;你经常会遇到需要在一定时间间隔内执行一些代码的情况。这时候&#xff0c;JavaScript 定时器就可以派上用场了。 什么是定时器&#xff1f; JS 定时器是…

[Gitops--2]Argocd和Gitlab-runner安装配置

ArgoCd Argo是一组k8s原生工具集,用于运行和管理k8s上的作业和应用程序.Argo提供了一种在k8s上创建工作和应用的三种计算模式:服务模式,工作流模式和基于事件模式.所有的Argo工具都实现为了创建控制器和自定义资源. 为什么选ArgoCD 应用程序的定义,配置和环境都应该是声明性…

ChatGPT和GPT-4帮你写人物传记

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

研读Rust圣经解析——Rust learn-11(测试,迭代器,闭包)

研读Rust圣经解析——Rust learn-11&#xff08;测试&#xff0c;迭代器&#xff0c;闭包&#xff09; 测试编写测试模块声明test模块编写测试方法执行测试测试结果检查 闭包定义一个闭包完整写法闭包可以捕获环境闭包类比函数闭包类型推断闭包获取所有权将被捕获的值移出闭包和…

Jenkins配置邮件通知

1、下载Email Extension插件 2、配置发件人邮箱地址 系统管理 > 系统配置 3、配置邮件通知 系统管理 > 系统配置 > 邮件通知 往下滑找到 通过发送测试邮件测试配置 测试 如果以上配置没有问题,会发送一封测试邮件到服务器中,如果有问题请优先检查一下端口号和是…

网络协议-HTTP协议详情讲解

目录 HTTP协议内容和方法 HTTP请求常见请求头 HTTP常见返回头 HTTP协议基本方法 常见HTTP状态码 面试解惑&#xff1a;301 vs 308 面试解惑&#xff1a;302 / 303 / 307 常见HTTP头 User-Agent Content-Type Origin Accept Referer Connection HTTP协议内容和方法…

Nginx中location规则 与 URL重写(rewrite)详解

1.Nginx中location与rewrite 1.1 location与rewrite常用的正则表达式 符号作用^匹配输入字符串的起始位置$ 匹配输入字符串的结束位置*匹配前面的字符零次或多次。如“ol*”能匹配“o”及“ol”、“oll” 匹配前面的字符一次或多次。如“ol”能匹配“ol”及“oll”、“olll”…

微信为什么使用 SQLite 保存聊天记录

SQLite “只是”一个库&#xff0c;它不是传统意义上的服务器。因此&#xff0c;在某些场合下&#xff0c;它确实不合适。但是&#xff0c;在相当多的其他场合&#xff0c;它却是最合适的选择。SQLite 号称是部署和使用最广泛的数据库引擎。我认为这很有可能&#xff0c;因为 S…

《PyTorch 深度学习实践》第10讲 卷积神经网络(基础篇)

文章目录1 卷积层1.1 torch.nn.Conv2d相关参数1.2 填充&#xff1a;padding1.3 步长&#xff1a;stride2 最大池化层3 手写数字识别该专栏内容为对该视频的学习记录&#xff1a;【《PyTorch深度学习实践》完结合集】 专栏的全部代码、数据集和课件全放在个人GitHub了&#xff…

SpringCloud之OpenFeign介绍案例+相关面试题

概述 OpenFeign是一个声明式的WEB服务客户端&#xff0c;它使WEB服务客户端变得更加容易。具有可插拔的注解支持&#xff0c;SpringCloud中添加了SpringMVC注解的支持。SpringCloud中集成了Ribbon和Eureka&#xff0c;以及SpringCloud LoadBalance&#xff0c;以便在使用Feign时…

C++数据结构:树

树 树是一种数据结构&#xff0c;它是n(n>0)个节点的有限集。n0时称为空树。n>0时&#xff0c;有限集的元素构成一个具有层次感的数据结构。 根 有且仅有一个结点的非空树&#xff0c;那个结点就是根。 A就是上面树的根节点 子树 在一棵非空树中&#xff0c;除根外&a…

由浅入深,一文彻底搞懂Mybatis+面试题分享

mybatis常见面试题链接&#xff1a;2023年-Mybatis常见面试题_是Smoky呢的博客-CSDN博客 MVC架构模式和三层架构 在说Mybatis之前&#xff0c;需要知道MVC架构模式和三层架构的这种思想 MVC架构模式 M&#xff1a;Model&#xff0c;数据层。都是和数据相关&#xff0c;比如实体…

MongoDB实现---事务机制

事务机制 原子性是MongoDB实现事务的难点&#xff0c;隔离性和持久性则是MongoDB事务机制的亮点 ACID支持&#xff1a;由于前面说过MongoDB是基于大数据、提供高度可扩展和高可用&#xff1b;所以其事务机制不仅仅是一般ACID还是结合了BASE理论下的ACID 原子性&#xff1a;保…

键盘录入及标识符

键盘录入 键盘录入介绍&#xff1a; ●为什么要有键盘录入? 目的&#xff1a;为了让我们操作的数据,变得更加灵活 举例&#xff1a;int a10; 这里a虽然是个变量&#xff0c;但记录的值&#xff0c;却是手动写死的。 提问&#xff1a;能不能让a变量记录的值&#xff0c;灵活…

Elasticsearch-mapping

1.Mapping基本概念 Mapping 也称之为映射&#xff0c;定义了 ES 的索引结构、字段类型、分词器等属性&#xff0c;是索引必不可少的组成部分。 ES 中的 mapping 有点类似与RDB中“表结构”的概念&#xff0c;在 MySQL 中&#xff0c;表结构里包含了字段名称&#xff0c;字段的…

# IMAGE - Image Perimeters

# IMAGE - Image Perimeters ## 题面翻译 ### 描述 给出一张由"x"和"."组成的矩阵。每个"x"可以向上下左右及两个斜对角进行连通&#xff0c;请问由某个点开始的"x"&#xff0c;它所连通的图形的周长为多少。 ### 输入 整个测试有多…

SpringBoot 整合ChatGPT API项目实战

准备工作 &#xff08;1&#xff09;已成功注册 OpenAI 的账号。 &#xff08;2&#xff09;创建 API KEY&#xff0c;这个 API KEY 是用于 HTTP 请求身份验证的&#xff0c;可以创建多个。 注意这个创建之后需要马上复制好保存&#xff0c;关闭弹框之后就看不到了。 &#xf…

excel的导入导出的两种方案 (1 EasyExcel 2 Hutool工具类)

文章目录 前言1 EasyExcel的导入导出导出1 导入依赖2 项目中的CourseEntity实体类3 CoureseVo VO类 (对CourseEntity进行EasyExcel导入导出操作)4 导出代码的编写 并最终测试导出效果5 (前端内容 可选)通过vue按钮点击 导出 导入1 导入依赖 跟导出相同2 创建回调监听器3 编写导…

1、Typescript基础入门与环境搭建

1、开发工具安装与基本配置 1.1、软件下载安装 如果你还没有使用过VSCode&#xff0c;当然先要去官网下载了。下载完成后双击安装&#xff0c;一直下一步即可。 1.2、编辑器汉化 如果你英语不是很好&#xff0c;配置中文版界面是很有必要的&#xff0c;安装个插件就可以了。打…