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注解就能快速的注冊一個(gè)定時(shí)任務(wù),但有的時(shí)候,我們業(yè)務(wù)上需要?jiǎng)討B(tài)創(chuàng)建,或者根據(jù)配置文件、數(shù)據(jù)庫里的配置去創(chuàng)建定時(shí)任務(wù)。這里有兩種思路,一種是自己實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度器或者第三方任務(wù)調(diào)度器如Quartz,另一種是使用Spring內(nèi)置的定時(shí)任務(wù)調(diào)度器ThreadPoolTaskScheduler,其實(shí)很簡單,從IOC容器中拿到對應(yīng)的Bean,然后去注冊定時(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)注冊、取消、獲取列表的方法。
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);
/**
* 注冊定時(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 單元測試
定時(shí)任務(wù)的單元測試不好測試,這里首先實(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ù)。具體的單元測試代碼如下:
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的場景一開始是為了動(dòng)態(tài)注冊路由(Controller),后來發(fā)現(xiàn)直接創(chuàng)建實(shí)例也可以注冊路由,不過這里也還要記錄一下,后面很多場景可能會(huì)用到。
2.1 SpringBeanUtils
這里封裝了一個(gè)utils用來獲取IOC容器中的Bean或者動(dòng)態(tài)注冊Bean到IOC中,實(shí)現(xiàn)很簡單從ApplicationContext中獲取BeanFactory,就可以注冊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 單元測試
創(chuàng)建一個(gè)靜態(tài)內(nèi)部類,用來注冊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è)場景是因?yàn)轫?xiàng)目中有個(gè)調(diào)用外部接口的單元測試,我又不想用mock方法,就想真實(shí)的測試一下HTTP請求的過程。一種是通過@RestController暴露一個(gè)接口,另一種就是動(dòng)態(tài)注冊路由。
3.1 SpringRouterUtils
動(dòng)態(tài)注冊controller實(shí)現(xiàn)很假單,通過RequestMappingHandlerMapping實(shí)例的registerMapping方法注冊即可。
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;
/**
* 路由注冊
* @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 單元測試
創(chuàng)建一個(gè)內(nèi)部類用來定義Controller層,然你后通過構(gòu)造RequestMappingInfo來定義請求路徑及方法。
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)容請搜索腳本之家以前的文章或繼續(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ù)注冊及動(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-09
SpringBoot使用JTA實(shí)現(xiàn)對多數(shù)據(jù)源的事務(wù)管理
了解事務(wù)的都知道,在我們?nèi)粘i_發(fā)中單單靠事務(wù)管理就可以解決絕大多數(shù)問題了,但是為啥還要提出JTA這個(gè)玩意呢,到底JTA是什么呢?他又是具體來解決啥問題的呢?本文小編就給大家介紹一下如何在Spring Boot中使用JTA實(shí)現(xiàn)對多數(shù)據(jù)源的事務(wù)管理2023-11-11
Spring Security自定義異常 AccessDeniedHandler不生效解決方法
本文主要介紹了Spring Security自定義異常 AccessDeniedHandler不生效解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Spring Data JPA踩坑記錄(@id @GeneratedValue)
這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(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分頁數(shù)據(jù)查詢的過程詳解
這篇文章主要給大家介紹了關(guān)于利用Spring MVC+Mybatis實(shí)現(xiàn)Mysql分頁數(shù)據(jù)查詢的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08
java框架基礎(chǔ)之SPI機(jī)制實(shí)現(xiàn)及源碼解析
這篇文章主要為大家介紹了java框架基礎(chǔ)之SPI機(jī)制實(shí)現(xiàn)及源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

