spring中定时任务相关源码分析

目录

前言

一、EnableScheduling

二、ScheduledAnnotationBeanPostProcessor

1.postProcessAfterInitialization

2.finishRegistration

3.ReschedulingRunnable

三、自定义任务线程池和定时任务

四、精准执行的定时任务


前言

spring中提供了定时任务的功能,简化了一些重复执行任务的编写过程,但同时也将内部细节隐藏了起来,因此,熟悉相关源码是必要的。定时任务的执行过程基本都是在ScheduledAnnotationBeanPostProcessor这个类中,基本是先扫描相关的注解,注册任务,然后将任务放入线程池中执行。



一、EnableScheduling

EnableScheduling注解的作用就是引入ScheduledAnnotationBeanPostProcessor这个类,下面的分析基本就是围绕这个类展开。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}
public class SchedulingConfiguration {

	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
		return new ScheduledAnnotationBeanPostProcessor();
	}

}

二、ScheduledAnnotationBeanPostProcessor



1.postProcessAfterInitialization

在postProcessAfterInitialization方法中,首先是扫描带有scheduled注解的方法,然后使用相关方法构建Runnable对象,构建task,并将task写入相关list中,后期调用。

	public Object postProcessAfterInitialization(Object bean, String beanName) {
		Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
		if (!this.nonAnnotatedClasses.contains(targetClass)) {
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
					new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
						@Override
						public Set<Scheduled> inspect(Method method) {
							//查找有相关注解的方法
							Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
									method, Scheduled.class, Schedules.class);
							return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
						}
					});
			if (annotatedMethods.isEmpty()) {
			}
			else {
				// Non-empty set of methods
				for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
					Method method = entry.getKey();
					for (Scheduled scheduled : entry.getValue()) {
						//生成任务并注册
						processScheduled(scheduled, method, bean);
					}
				}
			}
		}
		return bean;
	}

	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		try {
			Assert.isTrue(method.getParameterTypes().length == 0,
					"Only no-arg methods may be annotated with @Scheduled");

			Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
			//构造Runnable对象,run方法就是通过反射调用bean的method
			Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
			boolean processedSchedule = false;
			String errorMessage =
					"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

			Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);

			// Check cron expression
			String cron = scheduled.cron();
			if (StringUtils.hasText(cron)) {
				Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
				processedSchedule = true;
				String zone = scheduled.zone();
				if (this.embeddedValueResolver != null) {
					cron = this.embeddedValueResolver.resolveStringValue(cron);
					zone = this.embeddedValueResolver.resolveStringValue(zone);
				}
				TimeZone timeZone;
				if (StringUtils.hasText(zone)) {
					timeZone = StringUtils.parseTimeZoneString(zone);
				}
				else {
					timeZone = TimeZone.getDefault();
				}
				//将cron任务加入registrar的cronTasks中,方便后期调用
				tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
			}

	}

2.finishRegistration

        finishRegistration方法在onApplicationEvent方法中调用,在容器刷新完成后调用.。在该方法中,首先是调用所有的SchedulingConfigure的configureTasks方法,可以实现手动添加定时任务,也可以自定义任务线程池,当系统内没有自定义的任务线程池时,程序会先查找有没有默认线程池,如果还是没有就会创建一个单线程线程池执行任务。

	private void finishRegistration() {
		if (this.scheduler != null) {
			this.registrar.setScheduler(this.scheduler);
		}

		if (this.beanFactory instanceof ListableBeanFactory) {
			Map<String, SchedulingConfigurer> beans =
					((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
			List<SchedulingConfigurer> configurers = new ArrayList<SchedulingConfigurer>(beans.values());
			AnnotationAwareOrderComparator.sort(configurers);
			//调用SchedulingConfigurer的相关方法,可以自己手动加入定时方法,进行自定义操作
			for (SchedulingConfigurer configurer : configurers) {
				configurer.configureTasks(this.registrar);
			}
		}

		if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
			Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
			try {
				// Search for TaskScheduler bean... 对taskschedule进行赋值,前面taskschedule是null,所以手动写入的任务不会直接执行,当然在SchedulingConfigurer中定义线程池的话就不一样了
				this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));
			}
		}
		//将任务放入线程池中运行
		this.registrar.afterPropertiesSet();
	}
	protected void scheduleTasks() {
		if (this.taskScheduler == null) {
			//没有自定义任务线程池的情况下,使用单线程线程池
			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
			this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
		}
		if (this.triggerTasks != null) {
			for (TriggerTask task : this.triggerTasks) {
				addScheduledTask(scheduleTriggerTask(task));
			}
		}
		if (this.cronTasks != null) {
			for (CronTask task : this.cronTasks) {
				addScheduledTask(scheduleCronTask(task));
			}
		}

	}

3.ReschedulingRunnable

        程序最终会构建一个ReschedulingRunnable对象放入线程池中,在线程没有被取消时,会调用schedule方法,schedule方法会将当前对象再次放入线程池中,实现重复运行。

	public ScheduledFuture<?> schedule() {
		synchronized (this.triggerContextMonitor) {
			this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
			if (this.scheduledExecutionTime == null) {
				return null;
			}
			long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
			//调用jdk的定时任务线程池执行
			this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
			return this;
		}
	}

	public void run() {
		Date actualExecutionTime = new Date();
		//调用代理的run方法,实际执行定时业务逻辑
		super.run();
		Date completionTime = new Date();
		synchronized (this.triggerContextMonitor) {
			this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
			if (!this.currentFuture.isCancelled()) {
				//线程没有被取消时,调用schedule方法,再次将任务放入线程池中
				schedule();
			}
		}
	}

三、自定义任务线程池和定时任务

        程序ThreadPoolTaskScheduler的默认核心线程数是1,其在执行时调用的是juc的ScheduledThreadPoolExecutor执行,基于一个无界队列实现了延时执行,如果不定义线程数,会导致线程池中只有一个线程运行。

public class SchedulerExecutorConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(threadPoolTaskScheduler());
    }
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(20);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
}

四、精准执行的定时任务

       对于某些定时任务,要求在每分钟内都要执行,在没有配置自定义线程池的情况下,可能会与其他任务产生竞争,导致执行时间推迟。

    @Scheduled(cron = "0 */1 * * * ?")
    public void test1() throws InterruptedException {
        System.out.println("test1运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
        Thread.sleep(40000);
    }
    @Scheduled(cron = "0 */1 * * * ?")
    public void test2() throws InterruptedException {
        System.out.println("%%%%%%%%test2运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
        Thread.sleep(40000);
    }

如图所示定时任务,在只有test1运行时,能够保持一分钟执行一次。

         在只有test1和test2同时运行时,由于test1和test2都需要40秒钟,导致两个任务无法在一分钟内完成,导致test1无法保证能够一分钟运行一次。

       一般情况下,在使用自定义线程池,增加线程数量的情况下,能够改善这种情况,但是为了保证某些准确任务的独立运行,最好还是新建线程池,自定义定时任务线程池为其运行。

        可以使用juc的scheduledExecutorService为重要任务独自建立一个线程池,同时通过监听器在容器启动的时候将任务放入线程池中执行。

@Component("BeanDefineConfigue2")
public class BeanDefineConfigue2 implements ApplicationListener<ApplicationEvent> {
    boolean flag = false;
    List<String> list = new ArrayList<String>();
    ScheduledExecutorService scheduledExecutorService;

    /**
     * 当一个ApplicationContext被初始化或刷新触发
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(flag){
            return;
        }
        if (event instanceof ContextRefreshedEvent) {
            flag = true;
            System.out.println("spring容易初始化完毕================================================888");
            this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("test1运行"+ DateUtil.longToString(System.currentTimeMillis(),DateUtil.DateStyle.SQL_TIMESTAMP));
                    Thread.sleep(40000);
                }
            },1, 1, TimeUnit.MINUTES);
        }
    }


总结

本文主要对spring中定时任务的执行流程进行了分析,并且实现了自定义线程池,借助SchedulingConfigurer ,我们可以实现手动运行定时任务和手动取消,从而达到动态修改定时任务的目的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值