简单介绍
1.ShedLock是一个定时任务锁,并不是定时任务调度工具,更不是分布式任务调度程序。但是可以解决定时任务在多台服务器上重复执行的情况。
2.由spring boot提供的一个定时任务接口,实现该接口的bean,会在所有bean初始化结束后,调用configureTasks来配置定时任务。
3. 注意
- ShedLock不是任务调度工具,只是一个锁。需要任务调度工具要使用其他方法。
- 配合SchedulingConfigurer可以实现可控的定时任务,但并不是实时的控制,需要下次执行才会更改定时任务。如果有业务需要,可以通过判断参数手动执行定时任务,但不执行实际业务代码。
ShedLock
参考文档:https://github.com/lukas-krecan/ShedLock
项目很简单,总共分3步。
- 根据需要导入对应的项目,具体可以看github上的文章
<!-- 必须-->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.20.0</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>4.20.0</version>
</dependency>
- 写好配置类
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {
@Bean
public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
return new RedisLockProvider(connectionFactory, ENV);
}
}
- 写定时任务,简单的spring boot定时任务实现
@Component
public class SpringBootLockTask {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
//定时器时间配置 cron表达式
@Scheduled(cron = "0 0/1 * * * ? ")
//标识使用分布式锁
@SchedulerLock(name = "springBootLockTask1", lockAtMostFor = "50s", lockAtLeastFor = "50s")
public void expireOrderSchedule() {
logger.info("执行方法springBootLockTask1");
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("执行方法springBootLockTask1 ------------- 结束");
}
//定时器时间配置 cron表达式
@Scheduled(cron = "0 0/1 * * * ? ")
//标识使用分布式锁
@SchedulerLock(name = "springBootLockTask2", lockAtMostFor = "50s", lockAtLeastFor = "50s")
public void expireOrderSchedule2() {
logger.info("执行方法springBootLockTask2");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("执行方法springBootLockTask2 ------------ 结束");
}
}
完成,在启动项目后会开始分别执行两个定时任务,当两个定时任务的@SchedulerLock的注解的name相同时,expireOrderSchedule1和expireOrderSchedule2只能执行一个。
SchedulingConfigurer
- 实现接口,写一个公共方法,后面的定时任务可以直接继承该类
@Configuration
@EnableScheduling
public abstract class ConfigurerScheduling implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(
//执行定时任务 业务代码,可以在这里判断是否执行业务代码
this::execute,
//设置cron
triggerContext -> {
CronTrigger trigger = new CronTrigger(getCron());
return trigger.nextExecutionTime(triggerContext);
}
);
}
protected abstract void execute();
protected abstract String getCron();
}
- 实现抽象方法
在项目启动后,定时任务就会自动跑execute()里的内容。
@Configuration
public class TestTask extends ConfigurerScheduling {
@Override
protected void execute() {
System.out.println("TestTask定时任务开始了");
}
@Override
protected String getCron() {
//可以从数据库拿数据
return "0 0/1 * * * ? ";
}
}
配合使用
定时任务
@Component
public class TestTask extends ConfigurerScheduling {
@Override
@SchedulerLock(name = "TestTask", lockAtMostFor = "50s", lockAtLeastFor = "50s")
protected void execute() {
//业务代码
System.out.println("TestTask定时任务开始了");
}
@Override
protected String getName() {
//获取bean名称
return "testTask";
}
@Override
protected String getCron() {
//可以从数据库拿数据
return "0 0/1 * * * ? ";
}
}
抽象类,公共类
注意:因为spring boot代理的问题,这里只能再次获取bean,然后再调用execute()方法才有效,否则SchedulerLock注解失效且不会有异常
@Configuration
@EnableScheduling
public abstract class ConfigurerScheduling implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(
//执行定时任务 业务代码,可以在这里判断是否执行业务代码
this::executeToDo,
//设置cron
triggerContext -> {
CronTrigger trigger = new CronTrigger(getCron());
return trigger.nextExecutionTime(triggerContext);
}
);
}
private void executeToDo() {
// 因为spring boot代理的问题,这里只能再次获取bean,然后再调用execute()方法才有效,否则SchedulerLock注解失效且不会有异常
// ApplicationContext.getBean(); 通过getName获取bean方法
// execute(); 使用获取到的bean调用execute()方法
ConfigurerScheduling bean = (ConfigurerScheduling) SpringUtils.getBean(getName());
bean.execute();
}
protected abstract void execute();
protected abstract String getCron();
protected abstract String getName();
}
SpringUtils类,用于获取spring boot的bean
@Component
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {
applicationContext = applicationContextParam;
}
//通过名称获取bean
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
//通过类型获取bean
public <T> T getBean(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
//通过类型和名称获取bean
public static <T> T getBean(String beanName, Class<T> tClass) {
return applicationContext.getBean(beanName, tClass);
}
}