探索@Scheduled定时任务注解的实现机制

背景

   在《定时任务迁移到SnailJob的优化历程》文章中有针对基于xml文件配置定时任务迁移到SnailJob优化。但在今天对接另外一个系统,针对@Scheduled注解的定时任务迁移发现之前那种处理方式不可取,那@Scheduled运行机制到底是怎样的?

注册定时任务

  1. @EnableScheduling作为入口驱动加载定时任务,核心关注ScheduledAnnotationBeanPostProcessor
  2. ScheduledAnnotationBeanPostProcessor继承BeanPostProcessor,在实例化后置处理postProcessAfterInitialization进行判断是否定时任务的bean,是则进行注册
    2.1 下图展示注册定时任务主要流程(里面代码完全可以参考使用)
    // 展示相关代码
    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)));
    }
    
    2.2 下图展示注册流程详细流程
    (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)));
    	}
    }
    

启动定时任务

  1. ScheduledAnnotationBeanPostProcessor实现ApplicationListener接口,在Spring容器启动完毕后,会发送ContextRefreshedEvent事件,监听到该事件后启动定时任务(关注其finishRegistration方法)
  2. 下图展示启动定时任务主要步骤
    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();		
    }
    
  3. 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));
    		}
    	}		
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值