从零开始手写Lock4j框架(二)

接上一篇, 在上一篇中我们实现了一个大体的架子, 功能其实已经差不多了, 也非常接近V1.0.0版本了, 但是还有几个问题, 今天我们要解决一下。

Lock4j项目地址: https://gitee.com/baomidou/lock4j

key的生成

在上一篇中, 我们传入redis中的key, 设置成了这个样子, 不知道大家还记得不

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Lock4j {

    /**
     * KEY
     */
    String key() default "";

    /**
     * 过期时间 单位:毫秒
     */
    long expire() default 30000;

    /**
     * 尝试获取锁超时时间 单位:毫秒
     */
    long tryTimeout() default 3000;

}

这种情况下, 我们要想使用自己开发的这个分布式锁的组件, 就必须在方法上加上@Lock4j加上注解, 然后需要设置一个key的值; 同时还得保证所有用到分布式锁的地方的key的值的独立性, 比如有两个方法要使用不同的锁, 那么他们的key值肯定不能相同。这个时候我们很容易想到用类名+方法名来区分, 这样只要是同一个方法就能保证要获取的分布式锁是同一个, 这样是不是都完美了呢?

请大家考虑一下这个问题, 如果我需要对方法再做细粒度的区分呢, 比如一个获取用户信息的方法, 我想针对每一个用户设置分布式锁, 而不是一下子锁住所有的用户, 这样该怎么解决呢?

也就是说我们还需要除了类名+方法名之外, 允许用户设置个性化的东西, 而这些个性化的东西往往和方法中的参数有关; 有一个类似的场景就是spring cache设置缓存的时候, 在那里缓存的id是可以通过spel表达式指定方法中的参数啊啥的。我们也可以按照这种方式来改造一下。

首先, 我们的key不再是之前写死的形式了, 现在要变成用户想要的个性化的东西(spel表达式), 其次我们把key改为keys, 类型设置为一个数组, 方便扩展。那么现在我们的@Lock4j就变成了这个样:

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Lock4j {

    /**
     * KEY 默认包名+方法名
     */
    String[] keys() default "";

    /**
     * 过期时间 单位:毫秒
     */
    long expire() default 30000;

    /**
     * 尝试获取锁超时时间 单位:毫秒
     */
    long tryTimeout() default 3000;

}

然后, 我们需要定义一个自动生成key的类, 来根据类名, 方法名, 以及自定义的keys来生成最终需要存入到redis中的key, 我们把这个类叫做LockKeyGenerator, 具体实现如下:

public class LockKeyGenerator {

    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    private static final ExpressionParser PARSER = new SpelExpressionParser();

    public String getKeyName(MethodInvocation invocation, Lock4j lock4j) {
        StringBuilder sb = new StringBuilder();
        Method method = invocation.getMethod();
        sb.append(method.getDeclaringClass().getName()).append(".").append(method.getName());
        if (lock4j.keys().length > 1 || !"".equals(lock4j.keys()[0])) {
            sb.append(getSpelDefinitionKey(lock4j.keys(), method, invocation.getArguments()));
        }
        return sb.toString();
    }

    private String getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, NAME_DISCOVERER);
        List<String> definitionKeyList = new ArrayList<>(definitionKeys.length);
        for (String definitionKey : definitionKeys) {
            if (definitionKey != null && !definitionKey.isEmpty()) {
                String key = PARSER.parseExpression(definitionKey).getValue(context).toString();
                definitionKeyList.add(key);
            }
        }
        return StringUtils.collectionToDelimitedString(definitionKeyList, ".", "", "");
    }

}

相信这个类的逻辑大家都行看懂, 有必要提一下的是, 关于spel的解析, 我们调用了spring的一些工具类, 大家看一下就知道了。

我们有了这一个能生成key的工具类了, 那我们把之前的LockInterceptor的代码丰富一下

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    LockInfo lockInfo = null;
    try {
        Lock4j lock4j = invocation.getMethod().getAnnotation(Lock4j.class);
        // 生成key
        String keyName = lockKeyGenerator.getKeyName(invocation, lock4j);
        // 获取分布式锁
        lockInfo = lockExecutor.tryLock(keyName, lock4j.expire(), lock4j.tryTimeout());
        if (null != lockInfo) {
            // 执行业务逻辑
            return invocation.proceed();
        }
        return null;
    } finally {
        if (null != lockInfo) {
            // 释放分布式锁
            lockExecutor.releaseLock(lockInfo);
        }
    }
}

springboot自动装配

之前我们说过, 我们要适配spring, 现在使用最多的是springboot, 我们就改造成一个springboot的starter, 来提供给springboot的工程使用吧

首先, 我们要创建一个springboot的自动配置类LockAutoConfiguration, 代码如下

@Configuration
public class LockAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnClass(RedisTemplate.class)
    public LockExecutor lockExecutor() {
        return new RedisTemplateLockExecutor();
    }

    @Bean
    @ConditionalOnMissingBean
    public LockAnnotationAdvisor lockAnnotationAdvisor(LockInterceptor lockInterceptor) {
        return new LockAnnotationAdvisor(lockInterceptor);
    }

    @Bean
    @ConditionalOnMissingBean
    public LockInterceptor lockInterceptor(LockExecutor lockExecutor) {
        LockInterceptor lockInterceptor = new LockInterceptor();
        lockInterceptor.setLockExecutor(lockExecutor);
        return lockInterceptor;
    }

}

在配置类中, 我们把开发的相关组件注入到spring IOC容器中, 方便引入该分布式锁组件的应用来使用它, 这里面需要说一点的是 @ConditionalOnMissingBean这个注解, 是springboot的条件装配, 意思是当IOC容器中没有这个Bean的时候才注入, 这样就实现了自动装配了。

之后, 我们在resources目录下, 创建一个META-INF目录, 然后再创建一个spring.factories文件, 文件的内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration

意思是, LockAutoConfiguration是个需要自动装配的配置, 这样springboot在启动的时候会扫描并执行所有的这些配置。

好了, 到这里, V1.0.0版本的内容已经完成了, 其实还差一点, 我们需要把写的这个组件打包, 并且能发布到Maven仓库供别人使用才行, 那就是后话了, 今天先这样吧。

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值