手写单机版动态定时任务

118 阅读3分钟

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,怎么使用

使用教程:ducheng.github.io/dynamicSche…