接上一篇, 在上一篇中我们实现了一个大体的架子, 功能其实已经差不多了, 也非常接近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仓库供别人使用才行, 那就是后话了, 今天先这样吧。