背景
在《定时任务迁移到SnailJob的优化历程》文章中有针对基于xml文件配置定时任务迁移到SnailJob优化。但在今天对接另外一个系统,针对@Scheduled注解的定时任务迁移发现之前那种处理方式不可取,那@Scheduled运行机制到底是怎样的?
注册定时任务
- @EnableScheduling作为入口驱动加载定时任务,核心关注ScheduledAnnotationBeanPostProcessor
- ScheduledAnnotationBeanPostProcessor继承BeanPostProcessor,在实例化后置处理postProcessAfterInitialization进行判断是否定时任务的bean,是则进行注册
2.1 下图展示注册定时任务主要流程(里面代码完全可以参考使用)
2.2 下图展示注册流程详细流程// 展示相关代码 public Object postProcessAfterInitialization(Object bean, String beanName) { Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); // 获取该类中含有Scheduled注解的方法 Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> { Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations( method, Scheduled.class, Schedules.class); return (!scheduledMethods.isEmpty() ? scheduledMethods : null); }); // 注册定时任务 annotatedMethods.forEach((method, scheduledMethods) -> scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean))); }
(1)将bean和method封装在ScheduledMethodRunnable(实现Runnable)
(2)将runnable、fixedRate、initialDelay封装在FixedRateTask中
(3)将FixedRateTask(定时任务其中一种形式)统一封装为ScheduledTask中
(4)将FixedRateTask添加到registrar的任务集合属性中:fixedRateTasks// 下面以fixedRate为例进行分析 protected void processScheduled(Scheduled scheduled, Method method, Object bean) { // 将bean和method封装在ScheduledMethodRunnable(实现Runnable) Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass()); Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod); Set<ScheduledTask> tasks = new LinkedHashSet<>(4); long fixedRate = scheduled.fixedRate(); if (fixedRate >= 0) { // 在无参构造函数里会初始化this.registrar = new ScheduledTaskRegistrar() tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay))); } }
启动定时任务
- ScheduledAnnotationBeanPostProcessor实现ApplicationListener接口,在Spring容器启动完毕后,会发送ContextRefreshedEvent事件,监听到该事件后启动定时任务(关注其finishRegistration方法)
- 下图展示启动定时任务主要步骤
2.1 寻找容器中实现SchedulingConfigurer接口的bean
2.2 registrar通过SchedulingConfigurer配置任务执行器(线程池)private void finishRegistration() { // 寻找容器中实现SchedulingConfigurer接口的bean -> 主要registrar配置任务执行器(线程池) Map<String, SchedulingConfigurer> beans = ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class); List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values()); AnnotationAwareOrderComparator.sort(configurers); for (SchedulingConfigurer configurer : configurers) { // registrar配置任务执行器(线程池) configurer.configureTasks(this.registrar); } // registrar启动定时任务 this.registrar.afterPropertiesSet(); }
- ScheduledTaskRegistrar启动定时任务
3.1 在debug代码TaskUtils#decorateTaskWithErrorHandler时发现,可以指定异常处理器,默认是log方式
3.2 定时任务底层依赖JDK的ScheduledThreadPoolExecutor的scheduleAtFixedRate等方法protected void scheduleTasks() { // 没有配置定时任务线程池,则默认1个核心线程,Integer.MAX_VALUE的最大线程,DelayedWorkQueue队列 if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } if (this.fixedRateTasks != null) { for (IntervalTask task : this.fixedRateTasks) { // 关注ScheduledTaskRegistrar#scheduleFixedRateTask方法 --> 放入线程池中启动定时任务 addScheduledTask(scheduleFixedRateTask(task)); } } }