SpringBoot動(dòng)態(tài)定時(shí)任務(wù)、動(dòng)態(tài)Bean、動(dòng)態(tài)路由詳解
1 動(dòng)態(tài)定時(shí)任務(wù)
之前用過Spring中的定時(shí)任務(wù),通過@Scheduled注解就能快速的注冊(cè)一個(gè)定時(shí)任務(wù),但有的時(shí)候,我們業(yè)務(wù)上需要?jiǎng)討B(tài)創(chuàng)建,或者根據(jù)配置文件、數(shù)據(jù)庫(kù)里的配置去創(chuàng)建定時(shí)任務(wù)。這里有兩種思路,一種是自己實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度器或者第三方任務(wù)調(diào)度器如Quartz,另一種是使用Spring內(nèi)置的定時(shí)任務(wù)調(diào)度器ThreadPoolTaskScheduler,其實(shí)很簡(jiǎn)單,從IOC容器中拿到對(duì)應(yīng)的Bean,然后去注冊(cè)定時(shí)任務(wù)即可。下面以動(dòng)態(tài)管理cron任務(wù)為例介紹具體的實(shí)現(xiàn)方案。
1.1 定義CronTask實(shí)體
package org.example.dynamic.timed; import java.util.concurrent.ScheduledFuture; /** * 定時(shí)任務(wù) * * @author shirukai */ public class CronTask { private String id; private String cronExpression; private ScheduledFuture<?> future; private Runnable runnable; public String getId() { return id; } public String getCronExpression() { return cronExpression; } public ScheduledFuture<?> getFuture() { return future; } public Runnable getRunnable() { return runnable; } public void setFuture(ScheduledFuture<?> future) { this.future = future; } public static final class Builder { private String id; private String cronExpression; private ScheduledFuture<?> future; private Runnable runnable; private Builder() { } public static Builder aCronTask() { return new Builder(); } public Builder setId(String id) { this.id = id; return this; } public Builder setCronExpression(String cronExpression) { this.cronExpression = cronExpression; return this; } public Builder setFuture(ScheduledFuture<?> future) { this.future = future; return this; } public Builder setRunnable(Runnable runnable) { this.runnable = runnable; return this; } public CronTask build() { CronTask cronTask = new CronTask(); cronTask.id = this.id; cronTask.cronExpression = this.cronExpression; cronTask.future = this.future; cronTask.runnable = this.runnable; return cronTask; } } }
1.2 實(shí)現(xiàn)動(dòng)態(tài)任務(wù)調(diào)度器
該部分主要是獲取調(diào)度器實(shí)例,然后實(shí)現(xiàn)注冊(cè)、取消、獲取列表的方法。
package org.example.dynamic.timed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; /** * 動(dòng)態(tài)定時(shí)任務(wù)調(diào)度器 * * @author shirukai */ @Component @EnableScheduling public class CronTaskScheduler { @Autowired private ThreadPoolTaskScheduler scheduler; private final Map<String, CronTask> tasks = new ConcurrentHashMap<>(16); /** * 注冊(cè)定時(shí)任務(wù) * * @param task 任務(wù)的具體實(shí)現(xiàn) * @param expression cron表達(dá)式 * @return cronTask */ public CronTask register(Runnable task, String expression) { final CronTrigger trigger = new CronTrigger(expression); ScheduledFuture<?> future = scheduler.schedule(task, trigger); final String taskId = UUID.randomUUID().toString(); CronTask cronTask = CronTask.Builder .aCronTask() .setId(taskId) .setCronExpression(expression) .setFuture(future) .setRunnable(task) .build(); tasks.put(taskId, cronTask); return cronTask; } /** * 取消定時(shí)任務(wù) * * @param taskId 任務(wù)ID */ public void cancel(String taskId) { if (tasks.containsKey(taskId)) { CronTask task = tasks.get(taskId); task.getFuture().cancel(true); tasks.remove(taskId); } } /** * 更新定時(shí)任務(wù) * * @param taskId 任務(wù)ID * @param expression cron表達(dá)式 * @return cronTask */ public CronTask update(String taskId, String expression) { if (tasks.containsKey(taskId)) { CronTask task = tasks.get(taskId); task.getFuture().cancel(true); final CronTrigger trigger = new CronTrigger(expression); ScheduledFuture<?> future = scheduler.schedule(task.getRunnable(), trigger); task.setFuture(future); tasks.put(taskId, task); return task; } else { return null; } } /** * 獲取任務(wù)列表 * * @return List<CronTrigger> */ public List<CronTask> getAllTasks() { return new ArrayList<>(tasks.values()); } }
1.3 單元測(cè)試
定時(shí)任務(wù)的單元測(cè)試不好測(cè)試,這里首先實(shí)現(xiàn)一個(gè)需要被執(zhí)行的任務(wù),任務(wù)中會(huì)有一個(gè)CountDownLatch實(shí)例,主線程會(huì)等待countDown()方法執(zhí)行,說明定時(shí)任務(wù)被調(diào)度了,如果超時(shí)未執(zhí)行,說明定時(shí)任務(wù)未生效,此外還會(huì)定義一個(gè)AtomicInteger的計(jì)數(shù)器用來統(tǒng)計(jì)調(diào)用次數(shù)。具體的單元測(cè)試代碼如下:
package org.example.dynamic.timed; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.util.Assert; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @author shirukai */ @SpringBootTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class CronTaskSchedulerTest { @Autowired private CronTaskScheduler scheduler; final private static AtomicInteger counter = new AtomicInteger(); final private static CountDownLatch latch = new CountDownLatch(1); private static CronTask task; public static class CronTaskRunnable implements Runnable { @Override public void run() { System.out.println("The scheduled task is executed."); final int count = counter.incrementAndGet(); if (count <= 1) { latch.countDown(); } } } @Test @Order(1) void register() throws InterruptedException { CronTaskSchedulerTest.task = scheduler.register(new CronTaskRunnable(), "* * * * * ?"); boolean down = latch.await(2, TimeUnit.SECONDS); Assert.isTrue(down, "The scheduled task is not executed within 2 seconds."); } @Test @Order(4) void cancel() throws InterruptedException { if(CronTaskSchedulerTest.task!=null){ int minCount = counter.get(); scheduler.cancel(CronTaskSchedulerTest.task.getId()); TimeUnit.SECONDS.sleep(5); int maxCount = counter.get(); int deltaCount = maxCount - minCount; Assert.isTrue(deltaCount <= 1, "The scheduled task has not been cancelled."); } } @Test @Order(2) void update() throws InterruptedException { if (CronTaskSchedulerTest.task != null) { int minCount = counter.get(); CronTaskSchedulerTest.task = scheduler.update(CronTaskSchedulerTest.task.getId(), "*/2 * * * * ?"); TimeUnit.SECONDS.sleep(2); int maxCount = counter.get(); int deltaCount = maxCount - minCount; Assert.isTrue(deltaCount <= 1, "The scheduled task has not been update."); } } @Test @Order(3) void getAllTasks() { int count = scheduler.getAllTasks().size(); Assert.isTrue(count==1,"Failed to get all tasks."); } }
2 動(dòng)態(tài)Bean
動(dòng)態(tài)Bean的場(chǎng)景一開始是為了動(dòng)態(tài)注冊(cè)路由(Controller),后來發(fā)現(xiàn)直接創(chuàng)建實(shí)例也可以注冊(cè)路由,不過這里也還要記錄一下,后面很多場(chǎng)景可能會(huì)用到。
2.1 SpringBeanUtils
這里封裝了一個(gè)utils用來獲取IOC容器中的Bean或者動(dòng)態(tài)注冊(cè)Bean到IOC中,實(shí)現(xiàn)很簡(jiǎn)單從ApplicationContext中獲取BeanFactory,就可以注冊(cè)Bean了,ApplicationContext通過getBean就可以獲取Bean
package org.example.dynamic.bean; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; /** * Spirng Bean動(dòng)態(tài)注入 * * @author shirukai */ @Component public class SpringBeanUtils implements ApplicationContextAware { private static ConfigurableApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBeanUtils.context = (ConfigurableApplicationContext) applicationContext; } public static void register(String name, Object bean) { context.getBeanFactory().registerSingleton(name, bean); } public static <T> T getBean(Class<T> clazz) { return context.getBean(clazz); } }
2.2 單元測(cè)試
創(chuàng)建一個(gè)靜態(tài)內(nèi)部類,用來注冊(cè)Bean,然后通過工具類中的register和getBean方法來驗(yàn)證。
package org.example.dynamic.bean; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.util.Assert; import java.util.Objects; /** * @author shirukai */ @SpringBootTest class SpringBeanUtilsTest { public static class BeanTest { public String hello() { return "hello"; } } @Test void register() { SpringBeanUtils.register("beanTest",new BeanTest()); BeanTest beanTest = SpringBeanUtils.getBean(BeanTest.class); Assert.isTrue(Objects.equals(beanTest.hello(),"hello"),""); } }
3 動(dòng)態(tài)路由Controller
動(dòng)態(tài)路由這個(gè)場(chǎng)景是因?yàn)轫?xiàng)目中有個(gè)調(diào)用外部接口的單元測(cè)試,我又不想用mock方法,就想真實(shí)的測(cè)試一下HTTP請(qǐng)求的過程。一種是通過@RestController暴露一個(gè)接口,另一種就是動(dòng)態(tài)注冊(cè)路由。
3.1 SpringRouterUtils
動(dòng)態(tài)注冊(cè)controller實(shí)現(xiàn)很假單,通過RequestMappingHandlerMapping實(shí)例的registerMapping方法注冊(cè)即可。
package org.example.dynamic.router; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.reflect.Method; /** * 路由注冊(cè) * @author shirukai */ @Component public class SpringRouterUtils implements ApplicationContextAware { private static RequestMappingHandlerMapping mapping; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringRouterUtils.mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); } public static void register(RequestMappingInfo mapping, Object handler, Method method){ SpringRouterUtils.mapping.registerMapping(mapping,handler,method); } }
3.2 單元測(cè)試
創(chuàng)建一個(gè)內(nèi)部類用來定義Controller層,然你后通過構(gòu)造RequestMappingInfo來定義請(qǐng)求路徑及方法。
package org.example.dynamic.router; import org.apache.http.client.fluent.Form; import org.apache.http.client.fluent.Request; import org.apache.http.client.fluent.Response; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.util.pattern.PathPatternParser; import java.io.IOException; import java.lang.reflect.Method; import java.util.Objects; import static org.junit.jupiter.api.Assertions.*; /** * @author shirukai */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(properties = {"server.port=21199"}) class SpringRouterUtilsTest { public static class ExampleController { @ResponseBody public String hello(String name) { return "hi," + name; } } @Test void register() throws Exception { RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); options.setPatternParser(new PathPatternParser()); RequestMappingInfo mappingInfo = RequestMappingInfo .paths("/api/v1/hi") .methods(RequestMethod.POST) .options(options) .build(); Method method = ExampleController.class.getDeclaredMethod("hello", String.class); SpringRouterUtils.register(mappingInfo, new ExampleController(), method); Response response = Request.Post("http://127.0.0.1:21199/api/v1/hi") .bodyForm(Form.form().add("name", "xiaoming").build()) .execute(); Assert.isTrue(Objects.equals(response.returnContent().asString(), "hi,xiaoming"),""); } }
到此這篇關(guān)于SpringBoot動(dòng)態(tài)定時(shí)任務(wù)、動(dòng)態(tài)Bean、動(dòng)態(tài)路由詳解的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)定時(shí)任務(wù)和路由內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)設(shè)置動(dòng)態(tài)定時(shí)任務(wù)的方法詳解
- SpringBoot實(shí)現(xiàn)固定和動(dòng)態(tài)定時(shí)任務(wù)的三種方法
- Springboot實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)管理的示例代碼
- springboot通過SchedulingConfigurer實(shí)現(xiàn)多定時(shí)任務(wù)注冊(cè)及動(dòng)態(tài)修改執(zhí)行周期(示例詳解)
- SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例
- Springboot-admin整合Quartz實(shí)現(xiàn)動(dòng)態(tài)管理定時(shí)任務(wù)的過程詳解
相關(guān)文章
Java Socket聊天室編程(一)之利用socket實(shí)現(xiàn)聊天之消息推送
這篇文章主要介紹了Java Socket聊天室編程(一)之利用socket實(shí)現(xiàn)聊天之消息推送的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09SpringBoot使用JTA實(shí)現(xiàn)對(duì)多數(shù)據(jù)源的事務(wù)管理
了解事務(wù)的都知道,在我們?nèi)粘i_發(fā)中單單靠事務(wù)管理就可以解決絕大多數(shù)問題了,但是為啥還要提出JTA這個(gè)玩意呢,到底JTA是什么呢?他又是具體來解決啥問題的呢?本文小編就給大家介紹一下如何在Spring Boot中使用JTA實(shí)現(xiàn)對(duì)多數(shù)據(jù)源的事務(wù)管理2023-11-11Spring Security自定義異常 AccessDeniedHandler不生效解決方法
本文主要介紹了Spring Security自定義異常 AccessDeniedHandler不生效解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07Spring Data JPA踩坑記錄(@id @GeneratedValue)
這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07淺析Java的Spring框架中IOC容器容器的應(yīng)用
這篇文章主要介紹了Java的Spring框架中IOC容器容器的應(yīng)用,包括BeanFactory容器和ApplicationContext容器的介紹,需要的朋友可以參考下2015-12-12利用Spring MVC+Mybatis實(shí)現(xiàn)Mysql分頁(yè)數(shù)據(jù)查詢的過程詳解
這篇文章主要給大家介紹了關(guān)于利用Spring MVC+Mybatis實(shí)現(xiàn)Mysql分頁(yè)數(shù)據(jù)查詢的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08java框架基礎(chǔ)之SPI機(jī)制實(shí)現(xiàn)及源碼解析
這篇文章主要為大家介紹了java框架基礎(chǔ)之SPI機(jī)制實(shí)現(xiàn)及源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09