1,为什么要手写动态定时任务
在日常的开发过程中, 定时任务是很常见的。基本上都是使用注解形式的开发。但是它存在一些问题。 不能动态的修改启停和修改,还有手动执行。例如:在一个单体服务中,存在a,b,c 三个定时任务。A 定时任务是一个调用第三方收费服务的。某一天领导说,对方收费太贵了,要停止。这个时候只能把这个单体服务停止了,在改代码重新启动。这样很麻烦。要是这个时候,b,c 服务又要讲究实时性,类似订单超时自动取消的功能。这个时候就更不能随便的启停服务了。并且这种cron 表达式写在配置文件的硬编码模式也不太灵活。这个时候我就在思考。有没有一种可能,搞一个基于本地内存的单机版动态定时任务。
2,单机版动态定时任务的架构和思路
2,1 思路,还是使用自定义注解形式+自定义starter 代码替换灵活,修改少。
要对外提供web 接口实现基础的增删改查功能
2.2 具体的代码架构
annotation 的包里面都是自定义的注解, 里面包含两个注解,一个是使用在启动类上,表示开启动态定时任务, 另外一个是使用在方法上面。都是和springboot自带的定时任务注解很相似。
common 包里面是常量,主要是保存一些定时任务的信息和对应的线程信息
config 是配置类,也是核心类,里面使用了springboot 的 bean 的后置处理器。里面会注入service task 包下面的类。并且还会把一些定时任务信息组装放到常量池缓存里面。供service 进行任务的crud 的使用
task 包 是基础类,是真正的定时任务执行的类,里面封装了定时任务的元数据和自定义线程池。
utils 是工具类,下面主要是解析自定义注解的配置文件的key, 根据spel 表达式获取真正的值。
web包下面 就是定时任务的crud 的操作是通过调用 service来实现的,里面的controller 的注入是实现 ImportBeanDefinitionRegistrar 。自己实现了bean 的手动注入, 这里参考了阿里的druid 源代码。
3,主要代码
public class DynamicSchedulingAutoRegistryProcess implements BeanPostProcessor, InitializingBean {
@Autowired
private ConfigurablePropertyResolver propertyResolver;
private CustomCronTaskRegister customCronTaskRegister;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
if (methods == null) return bean;
for (Method method : methods) {
DynamicScheduled dcsScheduled = AnnotationUtils.findAnnotation(method, DynamicScheduled.class);
//初始化加载定时任务
if (!ObjectUtils.isEmpty(dcsScheduled)) {
String resolve = StrUtil.resolve(propertyResolver, dcsScheduled.cron());
DcSchedulingRunnable schedulingRunnable = new DcSchedulingRunnable(bean,beanName,method.getName(),dcsScheduled.desc(),resolve);
schedulingRunnable.setStatus(false);
customCronTaskRegister.initCronTask(schedulingRunnable);
}
}
return bean;
}
@Bean("dynamic-schedule-taskScheduler")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
//线程池使用的就是当前线程的大小
taskScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("dynamicScheduleThreadPool-");
return taskScheduler;
}
@Bean
public CustomCronTaskRegister getCustomCronTaskRegister() {
return this.customCronTaskRegister;
}
@Bean
public DynamicScheduleService DcDynamicScheduleService() {
return new DynamicScheduleService();
}
@Override
public void afterPropertiesSet() throws Exception {
customCronTaskRegister = new CustomCronTaskRegister();
}
/**
* 注册DynamicSchedule 的视图层到Spring的ioc
*/
public class DynamicScheduleControllerRegister implements ImportBeanDefinitionRegistrar {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public final void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String resetControllerRegName = DynamicScheduleController.class.getName();
// 判断是否被注册过
if (!existsBeanDefinition(resetControllerRegName,registry)) {
GenericBeanDefinition define = new GenericBeanDefinition();
define.setBeanClass(DynamicScheduleController.class);
define.setPrimary(true);
registry.registerBeanDefinition(resetControllerRegName, define);
log.info("Register DynamicScheduleController({}) with id:{}", define.getBeanClassName(), resetControllerRegName);
} else {
log.error("BeanDefinition id {} already exists in spring context", resetControllerRegName);
}
}
/**
* 判断是否注册过
* @param beanName
* @param registry
* @return
*/
private boolean existsBeanDefinition(String beanName, BeanDefinitionRegistry registry) {
try {
return registry.getBeanDefinition(beanName) != null;
} catch (NoSuchBeanDefinitionException e) {
return false;
}
}
}
/**
* 动态定时任务的视图层
*/
@RestController
@RequestMapping("/dynamicSchedule")
public class DynamicScheduleController {
@Autowired
private DynamicScheduleService dynamicScheduleService;
/**
* 查询所有动态定时任务列表
* @return
*/
@GetMapping("/getList")
public List<DcSchedulingRunnableVo> getList() {
List<DcSchedulingRunnable> runnableList = ConstantsPool.SCHEDULING_RUNNABLE_MAP.values().stream().collect(Collectors.toList());
List<DcSchedulingRunnableVo> runnableVos = new ArrayList<>();
runnableList.stream().forEach(x-> {
DcSchedulingRunnableVo runnableVo = new DcSchedulingRunnableVo();
BeanUtils.copyProperties(x,runnableVo);
runnableVos.add(runnableVo);
});
return runnableVos;
}
/**
* 动态修改定时任务版本
*/
@PostMapping("/updateSchedule")
public String updateSchedule(@RequestBody DcSchedulingRunnableDto dcSchedulingRunnableDto ) {
dynamicScheduleService.updateSchedule(dcSchedulingRunnableDto);
return "ok";
}
/**
* 开启动态定时任务
*/
@GetMapping("/startScheduleTask/{taskId}")
public String startScheduleTask(@PathVariable("taskId")String taskId ) {
dynamicScheduleService.startScheduleTask(taskId);
return "ok";
}
/**
* 取消动态定时任务
*/
@GetMapping("/removeCronTask/{taskId}")
public String removeCronTask(@PathVariable("taskId")String taskId ) {
dynamicScheduleService.removeCronTask(taskId);
return "ok";
}
/**
* 手动执行一次
*/
@GetMapping("/startOne/{taskId}")
public String startOneSchedule(@PathVariable("taskId") String taskId) {
DcSchedulingRunnable dcSchedulingRunnable = ConstantsPool.SCHEDULING_RUNNABLE_MAP.get(taskId);
Thread thread = new Thread(dcSchedulingRunnable);
thread.start();
return "ok";
}
}
4,怎么使用