Spring內(nèi)置任務(wù)調(diào)度如何實(shí)現(xiàn)添加、取消與重置詳解
前言
大家應(yīng)該都有所體會(huì),使用Spring的任務(wù)調(diào)度給我們的開發(fā)帶來了極大的便利,不過當(dāng)我們的任務(wù)調(diào)度配置完成后,很難再對(duì)其進(jìn)行更改,除非停止服務(wù)器,修改配置,然后再重啟,顯然這樣是不利于線上操作的,為了實(shí)現(xiàn)動(dòng)態(tài)的任務(wù)調(diào)度修改,我在網(wǎng)上也查閱了一些資料,大部分都是基于quartz實(shí)現(xiàn)的,使用Spring內(nèi)置的任務(wù)調(diào)度則少之又少,而且效果不理想,需要在下次任務(wù)執(zhí)行后,新的配置才能生效,做不到立即生效。本著探索研究的原則,查看了一下Spring的源碼,下面為大家提供一種Spring內(nèi)置任務(wù)調(diào)度實(shí)現(xiàn)添加、取消、重置的方法。話不多說了,來一起看看詳細(xì)的介紹 吧。
實(shí)現(xiàn)方法如下
首先,我們需要啟用Spring的任務(wù)調(diào)度
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd"> <task:annotation-driven executor="jobExecutor" scheduler="jobScheduler" /> <task:executor id="jobExecutor" pool-size="5"/> <task:scheduler id="jobScheduler" pool-size="10" /> </beans>
這一部分配置在網(wǎng)上是很常見的,接下來我們需要聯(lián)合使用@EnableScheduling與org.springframework.scheduling.annotation.SchedulingConfigurer
便攜我們自己的調(diào)度配置,在SchedulingConfigurer接口中,需要實(shí)現(xiàn)一個(gè)void configureTasks(ScheduledTaskRegistrar taskRegistrar);
方法,傳統(tǒng)做法是在該方法中添加需要執(zhí)行的調(diào)度信息。網(wǎng)上的基本撒謊那個(gè)也都是使用該方法實(shí)現(xiàn)的,使用addTriggerTask添加任務(wù),并結(jié)合cron表達(dá)式動(dòng)態(tài)修改調(diào)度時(shí)間,這里我們并不這樣做。
查看一下ScheduledTaskRegistrar源碼,我們發(fā)現(xiàn)該對(duì)象初始化完成后會(huì)執(zhí)行scheduleTasks()
方法,在該方法中添加任務(wù)調(diào)度信息,最終所有的任務(wù)信息都存放在名為scheduledFutures的集合中。
protected void scheduleTasks() { long now = System.currentTimeMillis(); if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } if (this.triggerTasks != null) { for (TriggerTask task : this.triggerTasks) { this.scheduledFutures.add(this.taskScheduler.schedule( task.getRunnable(), task.getTrigger())); } } if (this.cronTasks != null) { for (CronTask task : this.cronTasks) { this.scheduledFutures.add(this.taskScheduler.schedule( task.getRunnable(), task.getTrigger())); } } if (this.fixedRateTasks != null) { for (IntervalTask task : this.fixedRateTasks) { if (task.getInitialDelay() > 0) { Date startTime = new Date(now + task.getInitialDelay()); this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate( task.getRunnable(), startTime, task.getInterval())); } else { this.scheduledFutures.add(this.taskScheduler.scheduleAtFixedRate( task.getRunnable(), task.getInterval())); } } } if (this.fixedDelayTasks != null) { for (IntervalTask task : this.fixedDelayTasks) { if (task.getInitialDelay() > 0) { Date startTime = new Date(now + task.getInitialDelay()); this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay( task.getRunnable(), startTime, task.getInterval())); } else { this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay( task.getRunnable(), task.getInterval())); } } } }
所以我的思路就是動(dòng)態(tài)修改該集合,實(shí)現(xiàn)任務(wù)調(diào)度的添加、取消、重置。實(shí)現(xiàn)代碼如下:
package com.jianggujin.web.util.job; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.TriggerTask; import com.jianggujin.web.util.BeanUtils; /** * 默認(rèn)任務(wù)調(diào)度配置 * * @author jianggujin * */ @EnableScheduling public class DefaultSchedulingConfigurer implements SchedulingConfigurer { private final String FIELD_SCHEDULED_FUTURES = "scheduledFutures"; private ScheduledTaskRegistrar taskRegistrar; private Set<ScheduledFuture<?>> scheduledFutures = null; private Map<String, ScheduledFuture<?>> taskFutures = new ConcurrentHashMap<String, ScheduledFuture<?>>(); @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { this.taskRegistrar = taskRegistrar; } @SuppressWarnings("unchecked") private Set<ScheduledFuture<?>> getScheduledFutures() { if (scheduledFutures == null) { try { scheduledFutures = (Set<ScheduledFuture<?>>) BeanUtils.getProperty(taskRegistrar, FIELD_SCHEDULED_FUTURES); } catch (NoSuchFieldException e) { throw new SchedulingException("not found scheduledFutures field."); } } return scheduledFutures; } /** * 添加任務(wù) * * @param taskId * @param triggerTask */ public void addTriggerTask(String taskId, TriggerTask triggerTask) { if (taskFutures.containsKey(taskId)) { throw new SchedulingException("the taskId[" + taskId + "] was added."); } TaskScheduler scheduler = taskRegistrar.getScheduler(); ScheduledFuture<?> future = scheduler.schedule(triggerTask.getRunnable(), triggerTask.getTrigger()); getScheduledFutures().add(future); taskFutures.put(taskId, future); } /** * 取消任務(wù) * * @param taskId */ public void cancelTriggerTask(String taskId) { ScheduledFuture<?> future = taskFutures.get(taskId); if (future != null) { future.cancel(true); } taskFutures.remove(taskId); getScheduledFutures().remove(future); } /** * 重置任務(wù) * * @param taskId * @param triggerTask */ public void resetTriggerTask(String taskId, TriggerTask triggerTask) { cancelTriggerTask(taskId); addTriggerTask(taskId, triggerTask); } /** * 任務(wù)編號(hào) * * @return */ public Set<String> taskIds() { return taskFutures.keySet(); } /** * 是否存在任務(wù) * * @param taskId * @return */ public boolean hasTask(String taskId) { return this.taskFutures.containsKey(taskId); } /** * 任務(wù)調(diào)度是否已經(jīng)初始化完成 * * @return */ public boolean inited() { return this.taskRegistrar != null && this.taskRegistrar.getScheduler() != null; } }
其中用到的BeanUtils源碼如下:
package com.jianggujin.web.util; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class BeanUtils { public static Field findField(Class<?> clazz, String name) { try { return clazz.getField(name); } catch (NoSuchFieldException ex) { return findDeclaredField(clazz, name); } } public static Field findDeclaredField(Class<?> clazz, String name) { try { return clazz.getDeclaredField(name); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) { return findDeclaredField(clazz.getSuperclass(), name); } return null; } } public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) { try { return clazz.getMethod(methodName, paramTypes); } catch (NoSuchMethodException ex) { return findDeclaredMethod(clazz, methodName, paramTypes); } } public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) { try { return clazz.getDeclaredMethod(methodName, paramTypes); } catch (NoSuchMethodException ex) { if (clazz.getSuperclass() != null) { return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes); } return null; } } public static Object getProperty(Object obj, String name) throws NoSuchFieldException { Object value = null; Field field = findField(obj.getClass(), name); if (field == null) { throw new NoSuchFieldException("no such field [" + name + "]"); } boolean accessible = field.isAccessible(); field.setAccessible(true); try { value = field.get(obj); } catch (Exception e) { throw new RuntimeException(e); } field.setAccessible(accessible); return value; } public static void setProperty(Object obj, String name, Object value) throws NoSuchFieldException { Field field = findField(obj.getClass(), name); if (field == null) { throw new NoSuchFieldException("no such field [" + name + "]"); } boolean accessible = field.isAccessible(); field.setAccessible(true); try { field.set(obj, value); } catch (Exception e) { throw new RuntimeException(e); } field.setAccessible(accessible); } public static Map<String, Object> obj2Map(Object obj, Map<String, Object> map) { if (map == null) { map = new HashMap<String, Object>(); } if (obj != null) { try { Class<?> clazz = obj.getClass(); do { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { int mod = field.getModifiers(); if (Modifier.isStatic(mod)) { continue; } boolean accessible = field.isAccessible(); field.setAccessible(true); map.put(field.getName(), field.get(obj)); field.setAccessible(accessible); } clazz = clazz.getSuperclass(); } while (clazz != null); } catch (Exception e) { throw new RuntimeException(e); } } return map; } /** * 獲得父類集合,包含當(dāng)前class * * @param clazz * @return */ public static List<Class<?>> getSuperclassList(Class<?> clazz) { List<Class<?>> clazzes = new ArrayList<Class<?>>(3); clazzes.add(clazz); clazz = clazz.getSuperclass(); while (clazz != null) { clazzes.add(clazz); clazz = clazz.getSuperclass(); } return Collections.unmodifiableList(clazzes); } }
因?yàn)榧虞d的延遲,在使用這種方法自定義配置任務(wù)調(diào)度是,首先需要調(diào)用inited()
方法判斷是否初始化完成,否則可能出現(xiàn)錯(cuò)誤。
接下來我們來測(cè)試一下:
package com.jianggujin.zft.job; import java.util.Calendar; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; import com.jianggujin.web.util.job.DefaultSchedulingConfigurer; public class TestJob implements InitializingBean { @Autowired private DefaultSchedulingConfigurer defaultSchedulingConfigurer; public void afterPropertiesSet() throws Exception { new Thread() { public void run() { try { // 等待任務(wù)調(diào)度初始化完成 while (!defaultSchedulingConfigurer.inited()) { Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任務(wù)調(diào)度初始化完成,添加任務(wù)"); defaultSchedulingConfigurer.addTriggerTask("task", new TriggerTask(new Runnable() { @Override public void run() { System.out.println("run job..." + Calendar.getInstance().get(Calendar.SECOND)); } }, new CronTrigger("0/5 * * * * ? "))); }; }.start(); new Thread() { public void run() { try { Thread.sleep(30000); } catch (Exception e) { } System.out.println("重置任務(wù)............"); defaultSchedulingConfigurer.resetTriggerTask("task", new TriggerTask(new Runnable() { @Override public void run() { System.out.println("run job..." + Calendar.getInstance().get(Calendar.SECOND)); } }, new CronTrigger("0/10 * * * * ? "))); }; }.start(); } }
在該類中,我們首先使用一個(gè)線程,等待我們自己的任務(wù)調(diào)度初始化完成后向其中添加一個(gè)每五秒鐘打印一句話的任務(wù),然后再用另一個(gè)線程過30秒后修改該任務(wù),修改的本質(zhì)其實(shí)是現(xiàn)將原來的任務(wù)取消,然后再添加一個(gè)新的任務(wù)。
在配置文件中初始化上面的類
<bean id="defaultSchedulingConfigurer" class="com.jianggujin.web.util.job.DefaultSchedulingConfigurer"/> <bean id="testJob" class="com.jianggujin.zft.job.TestJob"/>
運(yùn)行程序,觀察控制臺(tái)輸出
這樣我們就實(shí)現(xiàn)了動(dòng)態(tài)的重置任務(wù)了。以上為個(gè)人探索出來的方法,如有更好的解決方案,歡迎指正。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Java任務(wù)調(diào)度的常見實(shí)現(xiàn)方法與比較詳解
- Java中Spring使用Quartz任務(wù)調(diào)度定時(shí)器
- java使用任務(wù)架構(gòu)執(zhí)行任務(wù)調(diào)度示例
- java多線程并發(fā)executorservice(任務(wù)調(diào)度)類
- Spring整合Quartz實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度的方法
- Spring整合TimerTask實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度
- springboot+Quartz實(shí)現(xiàn)任務(wù)調(diào)度的示例代碼
- Spring 中使用Quartz實(shí)現(xiàn)任務(wù)調(diào)度
- spring boot異步(Async)任務(wù)調(diào)度實(shí)現(xiàn)方法
- 使用java.util.Timer實(shí)現(xiàn)任務(wù)調(diào)度
相關(guān)文章
簡(jiǎn)單實(shí)現(xiàn)Spring的IOC原理詳解
這篇文章主要介紹了簡(jiǎn)單實(shí)現(xiàn)Spring的IOC原理詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12SpringBoot從2.7.x 升級(jí)到3.3注意事項(xiàng)
從SpringBoot 2.7.x升級(jí)到3.3涉及多個(gè)重要變更,特別是因?yàn)?nbsp;Spring Boot 3.x 系列基于 Jakarta EE 9,而不再使用 Java EE,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下2024-09-09Java項(xiàng)目防止SQL注入的幾種方法總結(jié)
SQL注入是比較常見的網(wǎng)絡(luò)攻擊方式之一,在客戶端在向服務(wù)器發(fā)送請(qǐng)求的時(shí)候,sql命令通過表單提交或者url字符串拼接傳遞到后臺(tái)持久層,最終達(dá)到欺騙服務(wù)器執(zhí)行惡意的SQL命令,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Java項(xiàng)目防止SQL注入的幾種方法,需要的朋友可以參考下2023-04-04SpringBoot通過redisTemplate調(diào)用lua腳本并打印調(diào)試信息到redis log(方法步驟詳解)
這篇文章主要介紹了SpringBoot通過redisTemplate調(diào)用lua腳本 并打印調(diào)試信息到redis log,本文分步驟給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02JDK8中的HashMap初始化和擴(kuò)容機(jī)制詳解
這篇文章主要介紹了JDK8中的HashMap初始化和擴(kuò)容機(jī)制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java多線程循環(huán)柵欄CyclicBarrier正確使用方法
這篇文章主要介紹了Java多線程循環(huán)柵欄CyclicBarrier正確使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法
這篇文章主要介紹了EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03