基于Reids与AOP实现的定时任务锁-ScheduledLock

mtain 2023年11月15日 215次浏览

转载文章:https://cloud.tencent.com/developer/article/2171832

1. 引入相关依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

2. 添加自定义注解接口

import java.lang.annotation.*;

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

3.AOP逻辑

import com.miracle.qaodo.annotation.ScheduledLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class ScheduleLockAspect implements ApplicationContextAware {
    //直接autowired redistemplate会报错Unexpected error occurred in scheduled task
    //因为@Scheduled注解方式级别高于资源注入级别,导致了资源注入失败
    //使用ApplicationContextAware,它实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static Object getBean(String name) {
        return getContext().getBean(name);
    }

    @Around(value = "@annotation(scheduledLock)")
    public Object around(ProceedingJoinPoint point, ScheduledLock scheduledLock) {
        //拦截的类名
        Class clazz = point.getTarget().getClass();
        //拦截的方法
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        log.info("定时任务锁 拦截了类:" + clazz + " 方法:" + method);
        Object proceed = null;
        RedisTemplate<String,String> redisTemplate = (RedisTemplate)getBean("redisTemplate");
        if (redisTemplate.opsForValue().setIfAbsent("qdchess-SchdulesLock-" + method.getName(), "lock",10, TimeUnit.SECONDS)) {
        //此处的key是可以根据自己的使用情况进行设置,只要方法之间不重复即可。
            log.info("其他服务未执行,通过执行");
            //获取锁,如果为false说明有其他服务正在执行,跳过执行
            try {
                proceed = point.proceed();          //执行定时任务
               redisTemplate.delete("qdchess-SchdulesLock-" + method.getName());
                return proceed;
            } catch (Throwable throwable) {
                redisTemplate.delete("qdchess-SchdulesLock-" + method.getName());
                throwable.printStackTrace();
                return null;
            }
        }
        log.info("其他服务已执行,未通过执行");
        return proceed;
    }
}

4. 使用

    @Scheduled(cron = "0,20,40 * * * * ?")
    @ScheduledLock
    public void testLock(){
        log.info(ServerTimer.getFull());
    }