工作业务中,有大量分布式加锁的重复代码,存在两个问题,一是代码重复率高,二是容易产生霰弹式修改,使用注解和AOP可以实现代码复用,简化分布式锁加锁和解锁流程。 @around注解是AspectJ框架提供的,允许我们在目标方法的执行前后进行代码增强。下面通过一个示例来介绍如何使用@around注解以及自定义注解实现加解锁(ReenTrantLock)简化。
- 改造前原代码 很经典的桥段,初始化10个线程,每个线程分别执行m++1000次,若想得到10000的结果,则必须加锁同步,加锁则需要在执行m++前进行加锁,在finally语句块中执行解锁操作,因此可以通过@around注解实现简化。
@Service @Slf4j public class HelloService { private int m = 0; private static int nums = 0; public ReentrantLock lock; // spel表达式 private static final String LOCK_KEY = "'asset_tenant_lock_prefix:' +" + "T(org.example.Common.ContextHelper).getTenantInfo().getOrgId()"; @Autowired HelloService helloService; public String doSomeSum() throws InterruptedException { nums++; log.info("调用次数:{}", nums); m = 0; CountDownLatch countDownLatch = new CountDownLatch(10); lock = new ReentrantLock(); for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { for (int j = 0; j < 1000; j++) { helloService.increM(); } countDownLatch.countDown(); }); thread.start(); } countDownLatch.await(); return String.valueOf(m); } @DistributeLock(key = LOCK_KEY) public void increM() { lock.lock(); try { m++; } finally { lock.unlock(); } } }
2. 改造第一步,创建一个注解 注解名为DistributeLock,有一个熟悉key,(这个例子是为了模仿使用Redis分布式锁)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {
String key();
}
3.改造第二步,定义一个切面类 切面类中,定义一个增强方法useDistributeLock,参数joinPoint是切入点,distributeLock是切入点形参,用于传入key。这里我们key使用的是Spel表达式,使用SpelExpressionParser可以求得最终的key值,joinPoint.proceed()执行的则是上文提到的原方法的执行内容,m++。
@Component
@Slf4j
public class DistributeLockAspect {
private static ReentrantLock lock = new ReentrantLock();
@Around("@annotation(distributeLock)")
public Object useDistributeLock(ProceedingJoinPoint joinPoint, DistributeLock distributeLock) throws Throwable {
lock.lock();
org.springframework.expression.ExpressionParser parser
= new SpelExpressionParser();
String lockKey = (String) parser.parseExpression(distributeLock.key()).getValue();
log.info(lockKey);
try {
return joinPoint.proceed();
} finally {
lock.unlock();
}
}
}
4.改造第三步,使用 使用很简单,只需要在需要增强的方法上加上我们创建的注解,并且给注解的属性Key一个字符串形式的Spel表达式即可实现加解锁操作。代码行数大大减少。
public class HelloService {
private int m = 0;
private static int nums = 0;
public ReentrantLock lock;
// spel表达式
private static final String LOCK_KEY = "'asset_tenant_lock_prefix:' +" + "T(org.example.Common.ContextHelper).getTenantInfo().getOrgId()";
@Autowired
HelloService helloService;
public String doSomeSum() throws InterruptedException {
nums++;
log.info("调用次数:{}", nums);
m = 0;
CountDownLatch countDownLatch = new CountDownLatch(10);
lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
helloService.increM();
}
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
return String.valueOf(m);
}
@DistributeLock(key = LOCK_KEY)
public void increM() {
m++;
}
}
5.最终测试结果 最终的测试结果是10000,说明加锁成功
来看看我们日志打印的Lock_key值是否符合预期,符合预期