基于Spring任务调度器实现可动态启停的任务调度器

来源:互联网 发布:快压解压缩软件 编辑:程序博客网 时间:2024/04/30 12:54

一. Spring任务调度介绍

介绍下Spring的任务调度,启动一个间隔1秒的定时任务,首先开启Spring定时任务:

import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableScheduling;@Configuration@EnableSchedulingpublic class TimerConfig {}

在@Configuration注解下添加@EnableScheduling就可以启动任务调度

然后配置定时任务:

import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class TimerTask {    @Scheduled(fixedDelay = 2000)    public void doTask() {        System.out.println("task executing");    }}

这里设置2秒执行间隔

最后启动Spring容器:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main {    public static void main(String[] args) throws InterruptedException {        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();        annotationConfigApplicationContext.scan("com.tubemogul.springsecuredemo.springDemo.basicTimer");        annotationConfigApplicationContext.refresh();    }}

在控制台中可以看到定时任务被执行了:
这里写图片描述

但是现在有个新需求,要求动态启停任务,也就是在程序中启动/停止定时任务,当然比较简单的做法是在定时任务中加个bool变量,动态改变这个变量来控制是否执行这个任务,但如果定时任务很多,代码就很丑了,而且一堆空任务会无谓的消耗计算机资源。

Spring调度器本身没有实现这个功能,下面我们就在Spring调度器的基础上,实现动态启停。

二. Spring 任务调度原理分析

在实现自己定制的任务调度器以前,先分析一下Spring任务调度器的原理。

我们从第一个配置看起@EnableScheduling,这是这个注解的源码:
这里写图片描述

关键在@Import注解,这个注解导入了SchedulingConfiguration类:
这里写图片描述

这也是一个配置类,在这个类中导入了ScheduledAnnotationBeanPostProcessor进入Spring容器,从名字上就可以看出这个类是一个BeanPostprocessor,了解Spring容器原理可知,后处理器的postProcessBeforeInitialization(Object bean, String beanName)方法和postProcessAfterInitialization(Object bean, String beanName)方法在Bean创建后被调用(这两个方法调用时间有细微差别,前者在自定义初始化代码生效前调用,后者在自定义初始化代码生效后调用)

有用的逻辑在postProcessBeforeInitialization(Object bean, String beanName)方法里面:
这里写图片描述

可以看出来实际就是扫描类的里面的方法是否包含了@Scheduled注解,然后调用processScheduled(scheduled, method, bean)注册调度任务,跟踪看看processScheduled(Scheduled scheduled, Method method, Object bean)方法,里面有一些对jdk代理和cglib代理的特殊处理,建立runnable对象,关键片段在这里:
这里写图片描述

现在去看看registrar对象是何方神圣
这里写图片描述

进入ScheduledTaskRegistrar类看看
这里写图片描述

可以看到实际所有的调度任务都被保存在这里。看看第一个变量private TaskScheduler taskScheduler; 阅读后面的源码可以知道,这个是真实的任务调度接口,这个接口有3个实现类:
这里写图片描述

从名字上看,我们先选择带线程池那个ThreadPoolTaskScheduler来看看:
这里写图片描述

终于找到了ScheduledExecutorService接口,原来Spring任务调度是基于ScheduledExecutorService实现的,这个是java.lang包里面提供的多线程调度工具类。

三. 实现可动态启停的任务调度器

前面已经分析了Spring调度器的原理,简单说就是定义一个BeanPostProcessor,扫描Bean方法中是否有任务调度的注解,如果有,则解析调度策略并则注册任务到TaskScheduler中进行调度。

OK,那么要实现可动态启停的思路就是自己实现一个BeanPostProcessor,自定义一个注解来标记需要调度的任务,然后向TaskScheduler中注册,然后提供一个启停任务的manager类,话不多说,直接上代码。
自定义一个任务标记注解MyFixDelaySchedule:

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface MyFixDelaySchedule {    long fixedDelay() default 60 * 60 * 1000;}

这里只实现了fixedDelay,其他的cron表达式,fixedRate等调度方式可以参考源码自己添加

修改一下TimerTask,使用自己的任务调度注解:

import org.springframework.stereotype.Component;@Componentpublic class TimerTask {    @MyFixDelaySchedule(fixedDelay = 2000)    public void doTask() {        System.out.println("task executing");    }}

然后是MyScheduledAnnotationBeanPostProcessor,一个BeanPostProcessor

import java.lang.reflect.Method;import java.util.Collections;import java.util.Map;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.beans.factory.DisposableBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.core.MethodIntrospector;import org.springframework.core.Ordered;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.scheduling.config.IntervalTask;import org.springframework.scheduling.support.ScheduledMethodRunnable;public class MyScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, DisposableBean {    @Autowired    private MySchedulingManager mySchedulingManager;    private final Set<Class<?>> nonAnnotatedClasses = Collections            .newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));    @Override    public int getOrder() {        return LOWEST_PRECEDENCE;    }    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        return bean;    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        Class<?> targetClass = AopUtils.getTargetClass(bean);        if (!this.nonAnnotatedClasses.contains(targetClass)) {            Map<Method, Set<MyFixDelaySchedule>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,                    new MethodIntrospector.MetadataLookup<Set<MyFixDelaySchedule>>() {                        @Override                        public Set<MyFixDelaySchedule> inspect(Method method) {                            Set<MyFixDelaySchedule> scheduledMethods = AnnotationUtils.getRepeatableAnnotations(method,                                    MyFixDelaySchedule.class, MyFixDelaySchedule.class);                            return (!scheduledMethods.isEmpty() ? scheduledMethods : null);                        }                    });            if (annotatedMethods.isEmpty()) {                this.nonAnnotatedClasses.add(targetClass);            } else {                // Non-empty set of methods                for (Map.Entry<Method, Set<MyFixDelaySchedule>> entry : annotatedMethods.entrySet()) {                    Method method = entry.getKey();                    for (MyFixDelaySchedule scheduled : entry.getValue()) {                        Runnable runnable = new ScheduledMethodRunnable(bean, method);                        IntervalTask task = new IntervalTask(runnable, scheduled.fixedDelay());                        mySchedulingManager.addFixedDelayTask(task);                    }                }            }        }        return bean;    }    @Override    public void destroy() throws Exception {        mySchedulingManager.stopScheduleJobs();    }}

写完BeanPostProcess后需要一个配置类把这个BeanPostProcess导入Spring容器:

import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Role;@Configuration@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public class MySchedulingConfiguration {    @Bean    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)    public MyScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {        return new MyScheduledAnnotationBeanPostProcessor();    }}

最后是任务控制类:

import java.util.ArrayList;import java.util.LinkedHashSet;import java.util.List;import java.util.Set;import java.util.concurrent.ScheduledFuture;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;import org.springframework.scheduling.config.IntervalTask;import org.springframework.stereotype.Component;@Componentpublic class MySchedulingManager {    private ThreadPoolTaskScheduler taskScheduler;    private List<IntervalTask> fixedDelayTasks = new ArrayList<IntervalTask>();    private final Set<ScheduledFuture<?>> scheduledFutures = new LinkedHashSet<ScheduledFuture<?>>();    public MySchedulingManager() {        taskScheduler = new ThreadPoolTaskScheduler();        taskScheduler.setPoolSize(4);        taskScheduler.initialize();        taskScheduler.getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true);    }    public void addFixedDelayTask(IntervalTask task) {        this.fixedDelayTasks.add(task);    }    public synchronized void startScheduleJobs() {        for (IntervalTask task : this.fixedDelayTasks) {            this.scheduledFutures.add(this.taskScheduler.scheduleWithFixedDelay(                    task.getRunnable(), task.getInterval()));        }    }    public synchronized void stopScheduleJobs() {        for (ScheduledFuture<?> future : this.scheduledFutures) {            future.cancel(true);        }        scheduledFutures.clear();    }}

写Main类,先启动任务,运行10秒后停止任务,再等10秒再启动任务,代码如下:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main {    public static void main(String[] args) throws InterruptedException {        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();        annotationConfigApplicationContext.scan("com.tubemogul.springsecuredemo.springDemo.basicTimer");        annotationConfigApplicationContext.refresh();        MySchedulingManager mySchedulingManager = annotationConfigApplicationContext.getBean(MySchedulingManager.class);        System.out.println("start job!");        mySchedulingManager.startScheduleJobs();        Thread.sleep(10000);        System.out.println("stop job!");        mySchedulingManager.stopScheduleJobs();        Thread.sleep(10000);        System.out.println("re-start job!");        mySchedulingManager.startScheduleJobs();    }}

运行结果如下:
这里写图片描述

这样我们就实现了可动态启停的任务调度器。
说明一点,Spring的任务调度器需要@EnableScheduling来生效是因为这个注解才能把SchedulingConfiguration导入Spring容器,而我自己实现这个MySchedulingConfiguration由于是放在com.tubemogul.springsecuredemo.springDemo.basicTimer包下面的,在执行annotationConfigApplicationContext.scan(“com.tubemogul.springsecuredemo.springDemo.basicTimer”);自动就会把MySchedulingConfiguration自动就被扫描进入Spring容器,所有就不需要类似@EnableScheduling的配置了

0 0
原创粉丝点击