java 如何在 Spring 中使用 EnableScheduling 注释在运行时重新启动计划任务?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31969251/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to restart scheduled task on runtime with EnableScheduling annotation in spring?
提问by Paul Fournel
I have been investigating how to change the frequency of a job on runtime with Java 8 and spring. This questionwas very useful but it did not totally solve my issue.
我一直在研究如何使用 Java 8 和 spring 更改运行时作业的频率。这个问题非常有用,但并没有完全解决我的问题。
I can now configure the date when to job should be executed next. But If set the delay to 1 year, then I need to wait 1 year before the new configuration in taken into account.
我现在可以配置下一个应该执行作业的日期。但是如果将延迟设置为 1 年,那么我需要等待 1 年才能将新配置考虑在内。
My idea would be to stop the scheduled task if the configuration value is changed (so from another class). Then recalculate the next time the task should be executed. Perhaps there is an easier way of doing this.
我的想法是如果配置值发生更改(因此来自另一个类),则停止计划任务。然后重新计算下一次应该执行任务的时间。也许有一种更简单的方法可以做到这一点。
Here is the code I have so far.
这是我到目前为止的代码。
@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();
}
}
);
}
}
This would be what I would like to do.
这将是我想做的事情。
@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";
}
}
采纳答案by Andrei I
- Create a singleton bean that gets an injected
TaskScheduler
. This will hold as state variables allScheduledFuture
s, likeprivate ScheduledFuture job1;
- On deployment, load from databases all schedule data and start the jobs, filling in all state variables like
job1
. - On change of scheduling data, cancelthe corresponding
Future
(e.gjob1
) and then start it again with the new scheduling data.
- 创建一个获取注入的单例 bean
TaskScheduler
。这将作为状态变量 allScheduledFuture
s,例如private ScheduledFuture job1;
- 在部署时,从数据库加载所有计划数据并启动作业,填写所有状态变量,如
job1
. - 调度数据发生变化时,取消相应的
Future
(例如job1
),然后用新的调度数据重新启动。
The key idea here is to get control on the Future
s as they are created, so to save them in some state variables, so that when something in scheduling data changes, you can cancel them.
这里的关键思想是在Future
创建 s 时对其进行控制,以便将它们保存在一些状态变量中,以便当调度数据中的某些内容发生变化时,您可以取消它们。
Here is the working code:
这是工作代码:
applicationContext.xml
应用上下文.xml
<task:annotation-driven />
<task:scheduler id="infScheduler" pool-size="10"/>
The singleton bean, that holds the Future
s
持有Future
s的单例 bean
@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);
}
}
}
回答by razor
What about using Set<ScheduledTask> ScheduledTaskRegistrar.getScheduledTasks()
to get all schedules tasks and calling ScheduledTask::cancel()
?
or maybe executing ThreadPoolTaskScheduler::shutdown()
and recreating ThreadPoolTaskScheduler and setting it again in ScheduledTaskRegistrar ?
使用Set<ScheduledTask> ScheduledTaskRegistrar.getScheduledTasks()
获取所有计划任务和调用ScheduledTask::cancel()
怎么样?或者可能执行ThreadPoolTaskScheduler::shutdown()
并重新创建 ThreadPoolTaskScheduler 并在 ScheduledTaskRegistrar 中再次设置?
回答by TGU
The following, an improved version of this code, seems a working POC based on Spring Boot.
以下是此代码的改进版本,似乎是基于 Spring Boot 的工作 POC。
1) In the main class, make sure scheduling is enabled:
1)在主类中,确保启用调度:
@SpringBootApplication
@EnableScheduling
2) Schedule an object that constantly polling a DB table that contains the schedule configuration, a cron like configuration in this case:
2)调度一个不断轮询包含调度配置的数据库表的对象,在这种情况下是一个类似cron的配置:
public class ScheduleConfigVo {
//some constructors, getter/setters
private Long id;
private String configValue;
}
3) I use mybatis, so the sheduled selection is something like:
3)我使用mybatis,所以调度选择是这样的:
@Mapper
public interface ScheduleConfigMapper {
ScheduleConfigVo getConfigureById(@Param("id") long id);
}
and
和
public class ScheduleConfigMapperImpl implements ScheduleConfigMapper {
@Scheduled(fixedDelay = 1000)
@Override
public ScheduleConfigVo getConfigureById(@Param("id") long id) {
return getConfigureById(id);
}
}
One may also chose to read from properties file for the fixedDelay value above. And a simple mybatis xml looks like:
也可以选择从属性文件中读取上面的 fixedDelay 值。一个简单的 mybatis xml 看起来像:
<!DOCTYPE mapper PUBLIC "-//rc.mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="yourPkg.ScheduleConfigMapper">
<select id="getConfigureById" resultType="yourPkg.ScheduleConfigVo">
select d.id as id, d.value as configValue
from scheduler d
where d.id = #{id}
</select>
</mapper>
4) create a table and populate it with a record
4)创建一个表并用记录填充它
CREATE TABLE "SCHEDULER" ( "ID" NUMBER(9,0), "VALUE" VARCHAR2(20 BYTE) )
创建表“调度程序”(“ID”NUMBER(9,0),“VALUE”VARCHAR2(20 BYTE))
and populated it with a record id=1, value=*/10 * * * * * => run like a cron every ten seconds
并用记录 id=1, value=*/10 * * * * * => 像 cron 一样每十秒运行一次
5) the scheduler part:
5)调度器部分:
@Service
public class DynamicScheduler implements SchedulingConfigurer {
ScheduledTaskRegistrar scheduledTaskRegistrar;
ScheduledFuture future;
@Autowired
ScheduleConfigMapper cronConf;
@Autowired
Task1 tsk1;
@Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(1);
scheduler.initialize();
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (scheduledTaskRegistrar == null) {
scheduledTaskRegistrar = taskRegistrar;
}
if (taskRegistrar.getScheduler() == null) {
taskRegistrar.setScheduler(poolScheduler());
}
if (repo.getConfigureById(1) != null) { //hard coded 1 for the moment
taskRegistrar.addTriggerTask(tsk1, (TriggerContext a) -> {
CronTrigger crontrigger = new CronTrigger(cronConf.getConfigureById(1).getConfigValue());
return crontrigger.nextExecutionTime(a);
});
} else { // if the entry is deleted from the table, need to de-register the task
Set<ScheduledTask> tasks = taskRegistrar.getScheduledTasks();
tasks.remove(tsk1);
}
}
}
6) a Runnable task that actually does the cron work
6) 一个实际执行 cron 工作的 Runnable 任务
@Slf4j
@Service
public class Task1 implements Runnable {
@Override
public void run() {
log.info("Task1 is running...");
}
}
One can schedule multiple different kinds of tasks in 5) above.
可以在上面的 5) 中安排多种不同类型的任务。
Once the application is started, the cron job will run. The running interval changes as the value in the table changes, and the job stops as the table entry is removed.
一旦应用程序启动,cron 作业就会运行。运行间隔随着表中值的变化而变化,作业随着表条目的删除而停止。
Note that if the job runs longer than the cron interval, the next run is after the previous job finishes. You can simulate this situation by adding, for instance, sleep 15 seconds in Task1 above to test it.
请注意,如果作业运行时间长于 cron 间隔,则下一次运行是在上一个作业完成之后。您可以通过在上面的 Task1 中添加例如 sleep 15 seconds 来模拟这种情况来测试它。
回答by drrob
One simple approach is to only ever add new tasks, not to try and cancel or restart the scheduler.
一种简单的方法是只添加新任务,而不是尝试取消或重新启动调度程序。
Each time the configuration changes, just add a new task with its new configuration.
每次配置更改时,只需添加一个具有新配置的新任务。
Then, whenever a task runs, it must first check some state (by querying database, or lookup up in a concurrent map, or whatever) to decide if it is the latest version. If it is, then it should proceed. Otherwise, it should end immediately.
然后,每当任务运行时,它必须首先检查某些状态(通过查询数据库,或在并发映射中查找,或其他方式)以确定它是否是最新版本。如果是,那么它应该继续。否则,它应该立即结束。
The only downside is that if you are changing job configuration frequently compared to how often they run, then of course the list of scheduled tasks will keep growing in memory.
唯一的缺点是,如果与作业的运行频率相比,您经常更改作业配置,那么计划任务列表当然会在内存中不断增长。