我一直在研究如何使用Java 8和spring在运行时更改作业的频率。 This question非常有用,但它并没有完全解决我的问题。
我现在可以配置下次执行作业的日期。但如果将延迟设置为1年,那么我需要在考虑新配置之前等待1年。
我的想法是在配置值发生变化时停止计划任务(所以从另一个类开始)。然后在下次执行任务时重新计算。也许有一种更简单的方法可以做到这一点。
这是我到目前为止的代码。
@Configuration
@EnableScheduling
public class RequestSchedulerConfig implements SchedulingConfigurer {
@Autowired
SchedulerConfigService schedulerConfigService;
@Bean
public RequestScheduler myBean() {
return new RequestScheduler();
}
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
@Override public void run() {
myBean().startReplenishmentComputation();
}
},
new Trigger() {
@Override public Date nextExecutionTime(TriggerContext triggerContext) {
Duration d = schedulerConfigService.getIntervalFromDB();
return DateTime.now().plus(d).toDate();
}
}
);
}
}
这就是我想做的事。
@RestController
@RequestMapping("/api/config/scheduler")
public class RequestSchedulerController {
@Autowired
ApplicationConfigWrapper applicationConfigWrapper;
@RequestMapping("/set/")
@ResponseBody
public String setRequestSchedulerConfig(@RequestParam(value = "frequency", defaultValue = "") final String frequencyInSeconds){
changeValueInDb(frequencyInSeconds);
myJob.restart();
return "Yeah";
}
}
答案 0 :(得分:7)
TaskScheduler
的单例bean。这将作为状态变量保留所有ScheduledFuture
s,如private ScheduledFuture job1;
job1
。Future
(例如job1
),然后使用新的计划数据再次启动它。这里的关键思想是在创建Future
时对其进行控制,以便将它们保存在某些状态变量中,这样当调度数据中的某些内容发生变化时,您可以取消它们。
以下是工作代码:
的applicationContext.xml
<task:annotation-driven />
<task:scheduler id="infScheduler" pool-size="10"/>
单例bean,它包含Future
s
@Component
public class SchedulerServiceImpl implements SchedulerService {
private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceImpl.class);
@Autowired
@Qualifier(value="infScheduler")
private TaskScheduler taskScheduler;
@Autowired
private MyService myService;
private ScheduledFuture job1;//for other jobs you can add new private state variables
//Call this on deployment from the ScheduleDataRepository and everytime when schedule data changes.
@Override
public synchronized void scheduleJob(int jobNr, long newRate) {//you are free to change/add new scheduling data, but suppose for now you only want to change the rate
if (jobNr == 1) {//instead of if/else you could use a map with all job data
if (job1 != null) {//job was already scheduled, we have to cancel it
job1.cancel(true);
}
//reschedule the same method with a new rate
job1 = taskScheduler.scheduleAtFixedRate(new ScheduledMethodRunnable(myService, "methodInMyServiceToReschedule"), newRate);
}
}
}
答案 1 :(得分:0)
一种简单的方法是只添加新任务,而不是尝试取消或重新启动调度程序。
每次配置更改时,只需使用新配置添加新任务。
然后,每当任务运行时,它必须首先检查某个状态(通过查询数据库,或在并发映射中查找,或其他任何)来确定它是否是最新版本。如果是,那么它应该继续。否则,它应立即结束。
唯一的缺点是,如果您经常更改作业配置与其运行频率相比,那么当然,计划任务列表将在内存中不断增长。
答案 2 :(得分:0)
使用Set<ScheduledTask> ScheduledTaskRegistrar.getScheduledTasks()
获取所有计划任务并调用ScheduledTask::cancel()
怎么样?
或执行ThreadPoolTaskScheduler::shutdown()
并重新创建ThreadPoolTaskScheduler并在ScheduledTaskRegistrar中再次进行设置?
答案 3 :(得分:0)
以下是this code的改进版本,似乎是基于Spring Boot的有效POC。您可以基于表配置多次启动和停止计划的任务。但是您不能从停止的地方开始停止的工作。
1)在主类中,请确保已启用调度,并可能将ThreadPoolTaskScheduler配置为大于一个的大小,以便调度的任务可以并行运行。
@SpringBootApplication
@EnableScheduling
@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(10);
scheduler.initialize();
return scheduler;
}
2)包含计划配置的对象,例如在这种情况下类似cron的配置:
public class ScheduleConfigVo {
//some constructors, getter/setters
private String taskName;
private String configValue; // like */10 * * * * * for cron
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScheduleConfigVo that = (ScheduleConfigVo) o;
return taskName.equals(that.taskName) &&
configValue.equals(that.configValue) ;
}
@Override
public int hashCode() {
return Objects.hash(taskName, configValue);
}
}
等于和hashCode,因为将进行对象比较。
3)我使用mybatis,所以选择的选择类似于:
@Mapper
public interface ScheduleConfigMapper {
List<ScheduleConfigVo> getAllConfigure();
}
和
public class ScheduleConfigMapperImpl implements ScheduleConfigMapper {
@Override
public List<ScheduleConfigVo>getAllConfigure() {
return getAllConfigure();
}
}
具有简单的伴随mybatis xml配置(此处未显示,但可以在Internet上的任何位置找到)。
4)创建一个表并用记录填充它
CREATE TABLE "SCHEDULER"
( "CLASS_NAME" VARCHAR2(100), --PK
"VALUE" VARCHAR2(20 BYTE) --not null
)
并用记录class_name = Task1,value = * / 10 * * * * *等填充它=>每十秒钟就会像cron一样运行
5)调度程序部分:
@Service
public class DynamicScheduler implements SchedulingConfigurer {
@Autowired
private ScheduleConfigMapper repo;
@Autowired
private Runnable [] tsks;
@Autowired
private TaskScheduler tsch;
private ScheduledTaskRegistrar scheduledTaskRegistrar;
private ScheduledFuture future;
private Map<String, ScheduledFuture> futureMap = new ConcurrentHashMap<>(); // for the moment it has only class name
List<ScheduleConfigVo> oldList = new ArrayList<>();
List<ScheduleConfigVo> newList;
List<ScheduleConfigVo> addList = new ArrayList<>();
List<ScheduleConfigVo> removeList = new ArrayList<>();
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (scheduledTaskRegistrar == null) {
scheduledTaskRegistrar = taskRegistrar;
}
if (taskRegistrar.getScheduler() == null) {
taskRegistrar.setScheduler(tsch);
}
updateJobList();
}
@Scheduled(fixedDelay = 5000)
public void updateJobList() {
newList = repo.getConfigure()== null ? new ArrayList<>() : repo.getConfigure();
addList.clear();
removeList.clear();
if (!newList.isEmpty()) {
//compare new List with oldList
if (!oldList.isEmpty()) {
addList = newList.stream().filter(e -> !oldList.contains(e)).collect(Collectors.toList());
removeList = oldList.stream().filter(e -> !newList.contains(e)).collect(Collectors.toList());
} else {
addList = new ArrayList<>(newList); // nothing to remove
}
} else { // nothing to add
if (!oldList.isEmpty()) {
removeList = new ArrayList<>(oldList);
} // else removeList = 0
}
log.info("addList="+ addList.toString());
log.info("removeList="+ removeList.toString());
//re-schedule here
for ( ScheduleConfigVo conf : removeList ) {
if ( !futureMap.isEmpty()){
future = futureMap.get(conf.getTaskName());
if (future != null) {
log.info("cancelling task "+conf.getTaskName() +" ...");
future.cancel(true);
log.info(conf.getTaskName() + " isCancelled = " + future.isCancelled());
futureMap.remove(conf.getTaskName());
}
}
}
for ( ScheduleConfigVo conf : addList ) {
for (Runnable o: tsks) {
if (o.getClass().getName().contains(conf.getTaskName())) { // o has fqn whereas conf has class name only
log.info("find " + o.getClass().getName() + " to add to scheduler");
future = scheduledTaskRegistrar.getScheduler().schedule(o, (TriggerContext a) -> {
CronTrigger crontrigger = new CronTrigger(conf.getConfigValue());
return crontrigger.nextExecutionTime(a);
});
futureMap.put(o.getClass().getName().substring(o.getClass().getName().lastIndexOf('.')+1), future);
}
}
}
oldList.clear();
oldList= newList;
}
6)实际执行cron工作的一个或多个可运行任务,例如:
@Slf4j
@Service
public class Task1 implements Runnable {
@Override
public void run() {
log.info("Task1 is running...");
}
}
启动应用程序后,cron作业将运行。运行间隔随表中值的更改而变化,作业因表条目被删除而停止。
请注意,如果作业的运行时间超过cron间隔,则下一次运行将在上一个作业完成之后进行。您可以通过在上面的Task1中添加例如15秒钟的睡眠时间进行测试来模拟这种情况。有时,在取消之后,一项工作可能仍会运行直到完成。
***只需编辑即可,如果人们喜欢lambda来保存一些行,则可以将上述removeList和addList修改为:
removeList.stream().filter(conf -> {
future = futureMap.get(conf.getTaskName());
return future != null;
}).forEach((conf) -> {
log.info("cancelling task " + conf.getTaskName() + " ...");
future.cancel(true);
log.info(conf.getTaskName() + " isCancelled = " + future.isCancelled());
});
和
Arrays.stream(tsks).forEach(task -> {
addList.stream().filter(conf -> task.getClass().getName().contains(conf.getTaskName())).forEach(conf -> {
log.info("find " + task.getClass().getName() + " to add to scheduler");
future = scheduledTaskRegistrar.getScheduler().schedule(task, (TriggerContext a) -> {
CronTrigger crontrigger = new CronTrigger(conf.getConfigValue());
return crontrigger.nextExecutionTime(a);
});
futureMap.put(task.getClass().getName().substring(task.getClass().getName().lastIndexOf('.') + 1), future);
});
});