• Spring @Scheduled执行原理解析


    项目使用很多@Scheduled(cron=**) 注解来实现定时任务,既然要用就必须弄清楚的它的实现原理,于是乎翻了一下相关的源码。

    Spring 3.0之后增加了调度器功能,提供的@Scheduled 注解, 那么它内部是如何实现的呢?

    本文以Spring 4.3.10.RELEASE 源码进行分析,相关源码在 org.springframework.scheduling 包下(spring-context)。

    核心类摘要:

    1. ScheduledAnnotationBeanPostProcessor
    2. ScheduledTaskRegistrar
    3. TaskScheduler
    4. ReschedulingRunnable

    源码分析

    首先,看一下 @Scheduled 注解定义:

    package org.springframework.scheduling.annotation;
    
    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Repeatable(Schedules.class)
    public @interface Scheduled {
    
    	String cron() default "";
    
    	String zone() default "";
    
    	long fixedDelay() default -1;
    
    	String fixedDelayString() default "";
    
    	long fixedRate() default -1;
    
    	String fixedRateString() default "";
    
    	long initialDelay() default -1;
    
    	String initialDelayString() default "";
    }
    

    支持cron表达式("cron" expression)、固定频率(fixedRate)、固定延时(fixedDelay) 3种调度方式。

    ScheduledAnnotationBeanPostProcessor

    ScheduledAnnotationBeanPostProcessor是@scheduled注解处理类,实现BeanPostProcessor接口(postProcessAfterInitialization方法实现注解扫描和类实例创建)、ApplicationContextAware接口(setApplicationContext方法设置当前ApplicationContext)、org.springframework.context.ApplicationListener(观察者模式,onApplicationEvent方法会被回调)。

    ScheduledAnnotationBeanPostProcessor postProcessAfterInitialization扫描所有@scheduled注解,区分cronTasks、fixedDelayTasks、fixedRateTasks。

    ScheduledAnnotationBeanPostProcessor 类定义如下:

    package org.springframework.scheduling.annotation;
    
    public class ScheduledAnnotationBeanPostProcessor
    		implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
    		Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
    		SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
    
    }
    

    ScheduledAnnotationBeanPostProcessor 实现BeanPostProcessor接口(postProcessAfterInitialization方法实现注解扫描和类实例创建)、ApplicationContextAware接口(setApplicationContext方法设置当前ApplicationContext)、org.springframework.context.ApplicationListener(观察者模式,onApplicationEvent方法会被回调)、DisposableBean接口(destroy方法中进行资源销毁操作)。

    postProcessAfterInitialization扫描所有@scheduled注解:

    	@Override
    	public Object postProcessAfterInitialization(final Object bean, String beanName) {
    		Class<?> targetClass = AopUtils.getTargetClass(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()) {
    				this.nonAnnotatedClasses.add(targetClass);
    				if (logger.isTraceEnabled()) {
    					logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());
    				}
    			}
    			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);
    					}
    				}
    				if (logger.isDebugEnabled()) {
    					logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
    							"': " + annotatedMethods);
    				}
    			}
    		}
    		return bean;
    	}
    

    processScheduled方法如下:

    
    	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 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);
    
    			// Determine initial delay
    			long initialDelay = scheduled.initialDelay();
    			String initialDelayString = scheduled.initialDelayString();
    			if (StringUtils.hasText(initialDelayString)) {
    				Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
    				if (this.embeddedValueResolver != null) {
    					initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
    				}
    				try {
    					initialDelay = Long.parseLong(initialDelayString);
    				}
    				catch (NumberFormatException ex) {
    					throw new IllegalArgumentException(
    							"Invalid initialDelayString value "" + initialDelayString + "" - cannot parse into integer");
    				}
    			}
    
    			// 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();
    				}
    				tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
    			}
    
    			// At this point we don't need to differentiate between initial delay set or not anymore
    			if (initialDelay < 0) {
    				initialDelay = 0;
    			}
    
    			// Check fixed delay
    			long fixedDelay = scheduled.fixedDelay();
    			if (fixedDelay >= 0) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
    			}
    			String fixedDelayString = scheduled.fixedDelayString();
    			if (StringUtils.hasText(fixedDelayString)) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				if (this.embeddedValueResolver != null) {
    					fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
    				}
    				try {
    					fixedDelay = Long.parseLong(fixedDelayString);
    				}
    				catch (NumberFormatException ex) {
    					throw new IllegalArgumentException(
    							"Invalid fixedDelayString value "" + fixedDelayString + "" - cannot parse into integer");
    				}
    				tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
    			}
    
    			// Check fixed rate
    			long fixedRate = scheduled.fixedRate();
    			if (fixedRate >= 0) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
    			}
    			String fixedRateString = scheduled.fixedRateString();
    			if (StringUtils.hasText(fixedRateString)) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				if (this.embeddedValueResolver != null) {
    					fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
    				}
    				try {
    					fixedRate = Long.parseLong(fixedRateString);
    				}
    				catch (NumberFormatException ex) {
    					throw new IllegalArgumentException(
    							"Invalid fixedRateString value "" + fixedRateString + "" - cannot parse into integer");
    				}
    				tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
    			}
    
    			// Check whether we had any attribute set
    			Assert.isTrue(processedSchedule, errorMessage);
    
    			// Finally register the scheduled tasks
    			synchronized (this.scheduledTasks) {
    				Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
    				if (registeredTasks == null) {
    					registeredTasks = new LinkedHashSet<ScheduledTask>(4);
    					this.scheduledTasks.put(bean, registeredTasks);
    				}
    				registeredTasks.addAll(tasks);
    			}
    		}
    		catch (IllegalArgumentException ex) {
    			throw new IllegalStateException(
    					"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
    		}
    	}
    

    finishRefresh方法触发所有监视者方法回调,方法如下:

    	@Override
    	public void afterSingletonsInstantiated() {
    		// Remove resolved singleton classes from cache
    		this.nonAnnotatedClasses.clear();
    
    		if (this.applicationContext == null) {
    			// Not running in an ApplicationContext -> register tasks early...
    			finishRegistration();
    		}
    	}
    
    	@Override
    	public void onApplicationEvent(ContextRefreshedEvent event) {
    		if (event.getApplicationContext() == this.applicationContext) {
    			// Running in an ApplicationContext -> register tasks this late...
    			// giving other ContextRefreshedEvent listeners a chance to perform
    			// their work at the same time (e.g. Spring Batch's job registration).
    			finishRegistration();
    		}
    	}
    
    	private void finishRegistration() {
    		if (this.scheduler != null) {
    			this.registrar.setScheduler(this.scheduler);
    		}
    
    		if (this.beanFactory instanceof ListableBeanFactory) {
    			Map<String, SchedulingConfigurer> configurers =
    					((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
    			for (SchedulingConfigurer configurer : configurers.values()) {
    				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...
    				this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));
    			}
    			catch (NoUniqueBeanDefinitionException ex) {
    				logger.debug("Could not find unique TaskScheduler bean", ex);
    				try {
    					this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, true));
    				}
    				catch (NoSuchBeanDefinitionException ex2) {
    					if (logger.isInfoEnabled()) {
    						logger.info("More than one TaskScheduler bean exists within the context, and " +
    								"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
    								"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
    								"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
    								ex.getBeanNamesFound());
    					}
    				}
    			}
    			catch (NoSuchBeanDefinitionException ex) {
    				logger.debug("Could not find default TaskScheduler bean", ex);
    				// Search for ScheduledExecutorService bean next...
    				try {
    					this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, false));
    				}
    				catch (NoUniqueBeanDefinitionException ex2) {
    					logger.debug("Could not find unique ScheduledExecutorService bean", ex2);
    					try {
    						this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, true));
    					}
    					catch (NoSuchBeanDefinitionException ex3) {
    						if (logger.isInfoEnabled()) {
    							logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
    									"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
    									"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
    									"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
    									ex2.getBeanNamesFound());
    						}
    					}
    				}
    				catch (NoSuchBeanDefinitionException ex2) {
    					logger.debug("Could not find default ScheduledExecutorService bean", ex2);
    					// Giving up -> falling back to default scheduler within the registrar...
    					logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
    				}
    			}
    		}
    
    		this.registrar.afterPropertiesSet();
    	}
    

    destroy方法暂停所有任务,如下:

    	@Override
    	public void destroy() {
    		synchronized (this.scheduledTasks) {
    			Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
    			for (Set<ScheduledTask> tasks : allTasks) {
    				for (ScheduledTask task : tasks) {
    					task.cancel();
    				}
    			}
    			this.scheduledTasks.clear();
    		}
    		this.registrar.destroy();
    	}
    

    @EnableScheduling注解

    接下来 ScheduledAnnotationBeanPostProcessor 是在哪儿生效的呢?我们来看一下@EnableScheduling 注解定义,如下:

    package org.springframework.scheduling.annotation;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(SchedulingConfiguration.class)
    @Documented
    public @interface EnableScheduling {
    
    }
    

    Spring Boot项目中 @EnableScheduling的魔法就在于 @Import(SchedulingConfiguration.class),看一下 SchedulingConfiguration源码:

    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class SchedulingConfiguration {
    
    	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
    		return new ScheduledAnnotationBeanPostProcessor();
    	}
    
    }
    
  • 相关阅读:
    神奇的C语言
    实现代码编辑器
    实现了一个简单的key-value存储系统
    一些官网说明
    苹果用户转入mate30,被恶心到了
    吐嘈一下乱用注入
    谈一下OOP的乱用现象
    mongo3.x ssl版安装文件
    再谈LRU双链表内存管理
    cocos在win平台exe无法使用 UserDefault 解决方法
  • 原文地址:https://www.cnblogs.com/kaleidoscope/p/11882612.html
Copyright © 2020-2023  润新知