【Spring】AOP——使用@around实现面向切面的方法增强

工作业务中,有大量分布式加锁的重复代码,存在两个问题,一是代码重复率高,二是容易产生霰弹式修改,使用注解和AOP可以实现代码复用,简化分布式锁加锁和解锁流程。 @around注解是AspectJ框架提供的,允许我们在目标方法的执行前后进行代码增强。下面通过一个示例来介绍如何使用@around注解以及自定义注解实现加解锁(ReenTrantLock)简化。

  1. 改造前原代码 很经典的桥段,初始化10个线程,每个线程分别执行m++1000次,若想得到10000的结果,则必须加锁同步,加锁则需要在执行m++前进行加锁,在finally语句块中执行解锁操作,因此可以通过@around注解实现简化。

    java 复制代码
    @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分布式锁)

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {
    String key();
}

3.改造第二步,定义一个切面类 切面类中,定义一个增强方法useDistributeLock,参数joinPoint是切入点,distributeLock是切入点形参,用于传入key。这里我们key使用的是Spel表达式,使用SpelExpressionParser可以求得最终的key值,joinPoint.proceed()执行的则是上文提到的原方法的执行内容,m++。

java 复制代码
@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表达式即可实现加解锁操作。代码行数大大减少。

java 复制代码
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值是否符合预期,符合预期

相关推荐
8***23553 分钟前
【wiki知识库】07.用户管理后端SpringBoot部分
java
bcbnb4 分钟前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
后端
开心就好20257 分钟前
iOS 上架 TestFlight 的真实流程复盘 从构建、上传到审核的团队协作方式
后端
小周在成长15 分钟前
Java 泛型支持的类型
后端
aiopencode16 分钟前
Charles 抓不到包怎么办?HTTPS 抓包失败、TCP 数据流异常与底层补抓方案全解析
后端
阿蔹19 分钟前
JavaWeb-Selenium 配置以及Selenim classnotfound问题解决
java·软件测试·python·selenium·测试工具·自动化
稚辉君.MCA_P8_Java21 分钟前
Gemini永久会员 C++返回最长有效子串长度
开发语言·数据结构·c++·后端·算法
哈哈哈笑什么26 分钟前
高并发分布式Springcloud系统下,使用RabbitMQ实现订单支付完整闭环的实现方案(反向撤销+重试+补偿)
分布式·spring cloud·rabbitmq
Penge66639 分钟前
Redis-bgsave浅析
redis·后端
阿白的白日梦44 分钟前
Windows下c/c++编译器MinGW-w64下载和安装
c语言·后端