The Beginning of everything (@enablescheduling)

  1. Let’s start with some sample code
@Configuration
@EnableScheduling
public class MainApplicationBootStrap {
	@Bean
	public Bride bride(a){
		return new Bride();
	}
	
	public static void main(String[] args) throws IOException {
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.lc.spring"); Bride bride = annotationConfigApplicationContext.getBean(Bride.class); System.out.println(bride); System.in.read(); }}/ / Bride class
public class Bride {
	private String name;
	private int count;
	private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd : HH mm ss");

	public void setName(String name) {
		this.name = name;
	}

	public String getName(a) {
		return name;
	}
    // Execute every five seconds.
	@Scheduled(cron = "0/5 * * * * ?" )
	public void sayHello2(a){
		System.out.println(simpleDateFormat.format(new Date())  + ":"  +  Thread.currentThread().getName()  + ":"+ Bride.class.getName() + ": say hello2 "+ count++ ); }}Copy the code

First look at @enablescheduling annotation inside what, then find a class above spring has been very clear to tell, this annotation function and related to the extension method, interested can download to see. I’m not going to write it here.

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

}
Copy the code

Note that the @import annotation imports the SchedulingConfiguration. The @import annotation is used in conjunction with @Configuration. Here will not analyze how to parse the configuration class in Spring, springBoot automatic assembly principle annotation @enableAutoConfiguration is the key to start here. I’ll write it later.

Go ahead and take a look at what SchedulingConfiguration is and what happens in it.

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) // In spring, you can use your own bean
public class SchedulingConfiguration {

	@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor(a) {
		return newScheduledAnnotationBeanPostProcessor(); }}Copy the code

Beans have three roles in Spring,

  1. ROLE_APPLICATION User-defined bean
  2. ROLE_SUPPORT Secondary roles
  3. ROLE_INFRASTRUCTURE represents a bean that has nothing to do with the user’s bean. ROLE_INFRASTRUCTURE represents a bean that is used by itself in the container.

Here is very sure, ScheduledAnnotationBeanPostProcessor is the focus of ScheduleTask implementation.

Case solved, so to sum up

Summarizes the configuration class on @ EnableScheduling annotations, @ EnableScheduling @ Import and aggregation in a ScheduledAnnotationBeanPostProcessor @ Import will Import.

The focus of the ScheduledAnnotationBeanPostProcessor (ScheduleTask implementation)

1. ScheduledAnnotationBeanPostProcessor class diagram

The following ScheduledAnnotationBeanPostProcessor implementation of the interface

  • MergedBeanDefinitionPostProcessor isBeanPostProcessor, on the basis of the BeanPostProcessor increased postProcessMergedBeanDefinition, this interface implementation class as follows, is one of the most importantAutowiredAnnotationBeanPostProcessorUsed to handle Autowired.
/ / after the instantiation out, will be called before calling postProcessAfterInitialization postProcessMergedBeanDefinition.
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class
        beanType, String beanName);

// before initialization
@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
// The key is this method. Will look at anything in ScheduledAnnotationBeanPostProcessor do.
@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
Copy the code
  • DestructionAwareBeanPostProcessorInheriting from BeanPostProcessor, two methods are added to determine whether the bean needs to be destroyed, and a previous call to destroy the bean.
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

default boolean requiresDestruction(Object bean) {
		return true;
	}
Copy the code
  • Implementing a lot of rAware interfaces, Aware interfaces have nothing to say.
  • SmartInitializingSingletonThe callback in Spring after the bean has been created.
/ / in all of the bean instantiation is completed, if the bean implementation SmartInitializingSingleton interface, will call afterSingletonsInstantiated method.
void afterSingletonsInstantiated(a)
Copy the code
  • ApplicationListener<ContextRefreshedEvent>Time monitor, the ContextRefreshedEvent in the paradigm represents a specific event of concern. Throughout the spring container is created, the object is also good, creating SmartInitializingSingleton call later, has been in the last of the last, will release ContextRefreshedEvent events.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the refresh method// Last step: publish corresponding event.finishRefresh(); ------------------------ Below is the details of finishRefresh./**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * {@link org.springframework.context.event.ContextRefreshedEvent}.
	 */
	protected void finishRefresh(a) {
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context. Initialize LifecycleProcessor
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first. 
		getLifecycleProcessor().onRefresh();

		// Publish the final event. Post the ContextRefreshedEvent event, indicating that the work is done.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
Copy the code
  • DisposableBeanWhen bean destroyed calls, bean lifecycle is destroyed, first call DestructionAwareBeanPostProcessor# postProcessBeforeDestruction, This is followed by the DisposableBean#destroy method, followed by the user-defined destroy method.

2. Several key methods for the above interface are described

As an aside, what is the general implementation of a timed task?

  1. Find any way to get the method modified by @schedule.
  2. Parsing the @schedule annotation on these methods saves the mapping.
  3. Schedule scheduled tasks based on triggered conditions. This logic is used to parse scheduled tasks in Spring.

1. Try every means to get the method modified by @schedule.

Relevant methods postProcessAfterInitialization

First of all, you know, postProcessAfterInitialization in SpringBean in that part of the life cycle, to solution depending on this problem, must first know what SpringBean life cycle? So this is tedious, life cycle documents are many, just look for a look. In simple terms, is the Spring in last step will call postProcessAfterInitialization initialization is complete. Of course, this is where the proxy object is created.

So, the following analysis of the source code, the source code is not difficult to understand, and I added a note

@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof AopInfrastructureBean) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}
		Cglib is enhanced by inheritance. Cglib is enhanced by inheritanceClass<? > targetClass = AopProxyUtils.ultimateTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass)) {
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,// All the methods that have been Scheduled annotations have been found
					(MethodIntrospector.MetadataLookup<Set<Scheduled>>) 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: "+ targetClass); }}else {
				// Non-empty set of methods
				annotatedMethods.forEach((method, scheduledMethods) ->
						scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
				if (logger.isDebugEnabled()) {
					logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
							"'."+ annotatedMethods); }}}return bean;
	}
Copy the code

2. Parse the @Schedule annotations on these methods and save the mapping (processScheduled method parsing)

	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		try {
			// asserts whether the order method has arguments, and if so, an error is reported
			Assert.isTrue(method.getParameterCount() == 0."Only no-arg methods may be annotated with @Scheduled");GetParameterCount ==0, if not 0, an error is reported. This is also a weak method in Spring for scheduled tasks, but I don't see any problem with this, who needs to pass parameters when executing a scheduled task
			// Check if this method is static, private,
			Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
			// Encapsulate the method to be executed as ScheduledMethodRunnable.
			/ / target said bean
			//method Indicates the method to be executed
			Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
			// The @scheduled annotation is applied and parsed to the arguments, which are false and will be decided later.
			boolean processedSchedule = false;
			String errorMessage =
					"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

			// Store the assembled ScheduledTask,
			Set<ScheduledTask> tasks = new LinkedHashSet<>(4);

			/ / determine initialDelay
			long initialDelay = scheduled.initialDelay();
			String initialDelayString = scheduled.initialDelayString();
			if (StringUtils.hasText(initialDelayString)) {
				Assert.isTrue(initialDelay < 0."Specify 'initialDelay' or 'initialDelayString', not both");
				"Pt20.345s" -- parses as "20.345 seconds" "PT15M" -- parses as "15 minutes" (where a minute is 60
				if (this.embeddedValueResolver ! =null) {
					// This means that the initialDelayString can be used to write SPEL expressions. Embeddedvalue Solver processors are common and replace values from the environment
					initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
				}
				if (StringUtils.hasLength(initialDelayString)) {
					try {InitialDelay (initialDelay);
						initialDelay = parseDelayAsLong(initialDelayString);
					}
					catch (RuntimeException ex) {
						throw new IllegalArgumentException(
								"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long"); }}}// Check the cron expression
			String cron = scheduled.cron();
			if (StringUtils.hasText(cron)) {
				String zone = scheduled.zone();
				if (this.embeddedValueResolver ! =null) {// This can also be handled here
					// The embeddedValueResolver is an embeddedValueResolver.
					cron = this.embeddedValueResolver.resolveStringValue(cron);
					zone = this.embeddedValueResolver.resolveStringValue(zone);
				}
				if (StringUtils.hasLength(cron)) {
					Assert.isTrue(initialDelay == -1."'initialDelay' not supported for cron triggers");

					// After parsing to cron, the flag bit becomes true
					processedSchedule = true;
					TimeZone timeZone;
					if (StringUtils.hasText(zone)) {
						timeZone = StringUtils.parseTimeZoneString(zone);
					}
					else {
						timeZone = TimeZone.getDefault();
					}
					// Change the cron expression to CronTrigger,
					// Change runnable (ScheduledMethodRunnable) to CronTask
					// Change CronTask to scheduleCronTask and add CronTask to the Registrar cronTasks property and maintain the mapping between CronTask and scheduleCronTask.
					tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, newCronTrigger(cron, timeZone)))); }}// At this point we don't need to differentiate between initial delay set or not anymore
			if (initialDelay < 0) {
				initialDelay = 0; }... Omit some code....... This code is the same as cron above, except that the task type is different. Everything else is the same.// Check whether we had any attribute set
			Assert.isTrue(processedSchedule, errorMessage);

			// After parsing, store the bean and @schedule in the map. The key of the map is the bean and the value is set. The set is the set of the bean marked by @schedule.
			// ScheduledTask collection
			// If you want to add a lock, you need to add a lock.
			// Is there a concurrency problem here? First of all, he is work in postProcessAfterInitialization method, this method works when analytical bean in the spring
			// Spring calls beanPostProcess sequentially. There is no concurrency problem.
			// So I don't understand the lock.
			synchronized (this.scheduledTasks) {
				Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4)); regTasks.addAll(tasks); }}catch (IllegalArgumentException ex) {
			throw new IllegalStateException(
					"Encountered invalid @Scheduled method '" + method.getName() + "'."+ ex.getMessage()); }}Copy the code

Question:

  1. I don’t understand the operation of adding lock in the above code, please inform me.

Description:

For a few important class descriptions in the code above

  1. ScheduledMethodRunnable

    1. The method that encapsulates the bean and @schedule annotations encapsulates them as a Runnable.
  2. CronTrigger

    1. Trigger stands for Trigger. In the @schedule annotation, each type corresponds to a different Trigger.

The core method in Trigger is' Date nextExecutionTime(TriggerContext TriggerContext); Periodperiodgger The time of the next execution is calculated for both Cron and PeriodTigger.Copy the code
  1. CronTask

    1. Task is a class, not an interface.

      The core task method is getRunnable. TiggerTask adds getTrigger and CronTask adds getExpression ().

  2. ScheduledTask

    1. First of all, it is final

    2. There are two property values

      	private final Task task;/ / task
      
      
          ScheduledFuture is the reference object after scheduledExecutorService submits the task.
      	//ScheduledFuture
                  scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
      	//	}, 0, 0, TimeUnit.SECONDS);
      	@Nullable
      	volatileScheduledFuture<? > future;Copy the code
    3. In general, ScheduledTask retains reference objects and task tasks after the task has been submitted to scheduledExecutorServic.

  3. ScheduledTaskRegistrar

  1. InitializingBean is called before the custom init method is called
  2. Call To DisposableBean before calling the custom destroy method
  3. ScheduledTaskHolder, this interface is used to get all scheduledTasks

ScheduledTaskRegistrar can register ScheduledTask via ScheduledTaskRegistrar and also obtain ScheduledTask. Therefore, the focus of the next phase is to look at the ScheduledTaskRegistrar logic

The ScheduledTask annotation is registered via ScheduledTaskRegistrar to the ScheduledTaskRegistrar via ScheduledTaskRegistrar.

3. Schedule scheduled tasks based on triggered conditions. (onApplicationEvent)

Said earlier, ScheduledAnnotationBeanPostProcessor realizes the ApplicationListener < ContextRefreshedEvent > interface, Spring will after all the work is done, Publish a ContextRefreshedEvent event. That’s the point. Now what’s going on inside it

	@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(); }}Copy the code

FinishRegistration is all about it. Continue to trend

protected void scheduleTasks(a) {
		// If spring does not have an implementation class for TaskScheduler or ScheduledExecutorService, use the default one.
		// Executors.newSingleThreadScheduledExecutor(); The core thread is 1, but the maximum number of threads is Max.
		// Wrapped by ConcurrentTaskScheduler.
		if (this.taskScheduler == null) {
			this.localExecutor = Executors.newSingleThreadScheduledExecutor();
			this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
		}
		// The scheduledTask registrar wants to obtain the scheduledTask via the two-way mapping via the ScheduledTaskRegistrar
		// ScheduledTaskApply submit the task via ScheduledTaskRegistrar, save the Future object in the ScheduledTask reference.
		if (this.triggerTasks ! =null) {
			for (TriggerTask task : this.triggerTasks) { addScheduledTask(scheduleTriggerTask(task)); }}if (this.cronTasks ! =null) {
			for (CronTask task : this.cronTasks) { addScheduledTask(scheduleCronTask(task)); }}if (this.fixedRateTasks ! =null) {
			for (IntervalTask task : this.fixedRateTasks) { addScheduledTask(scheduleFixedRateTask(task)); }}if (this.fixedDelayTasks ! =null) {
			for (IntervalTask task : this.fixedDelayTasks) { addScheduledTask(scheduleFixedDelayTask(task)); }}}Copy the code

ScheduleCronTask looks at the code logic inside it, but everything else is pretty much the same. The difference is that the type of task may be different and the method of submitting a taskSchedule may be different

// First the method is ScheduledTaskRegistrar
@Nullable
	public ScheduledTask scheduleCronTask(CronTask task) {
        // The CronTask and ScheduledTask references that were saved when the schedule was resolved.
		ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
		boolean newTask = false;
        NewTask must be fasle, and the return value of this method must be null
		if (scheduledTask == null) {
			scheduledTask = new ScheduledTask(task);
			newTask = true;
		}
        // Submit the task
		if (this.taskScheduler ! =null) {
            TaskScheduler (); // submit tasks
			scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
		}
		else {
			addCronTask(task);
			this.unresolvedTasks.put(task, scheduledTask);
		}
		return (newTask ? scheduledTask : null);
	}

Copy the code

The logic above is the entire process of submission, but let’s take a look at TaskScheduler. The final mission must have been carried out through him. A top priority

4. TaskScheduler makes analysis a priority

Note: The default implementation of this interface is ThreadPoolTaskScheduler. ThreadPoolTaskScheduler and cronTask are explained in detail below

The detailed methods in the Interface of the TaskScheduler task are not shown here because there are too manyScheduledFuture<? > schedule(Runnable task, Trigger trigger);Let me elaborate

** Note: ** is not created automatically in Spring by default, you must manually declare @bean, which InitializingBean and DisposableBean. There must be initialization and destruction

Go ahead and see the schedule method details above

@Override @Nullable public ScheduledFuture<? > the schedule (Runnable task, Trigger, the Trigger) {/ / / / get actuators on the actuator can see ExecutorConfigurationSupport parameter setting,  ScheduledExecutorService executor = getScheduledExecutor(); Try {// Handle for error handling. There are two types of handles. One is to print logs after errors occur. One is to throw an exception // LoggingErrorHandler // PropagatingErrorHandler ErrorHandler ErrorHandler = this. ErrorHandler; if (errorHandler == null) { errorHandler = TaskUtils.getDefaultErrorHandler(true); } // Wrap the actuator, task, trigger as ReschedulingRunnable, schedule by submitting the task to the executor, Return new ReschedulingRunnable(Task, trigger, executor, errorHandler).schedule(); } catch (RejectedExecutionException ex) { throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex); }}Copy the code

supplement

  1. Create executor threads to see ExecutorConfigurationSupport related logic

    The parameters for thread pool configuration are listed here

    1. RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();

    2. The priority of the thread is 5

    3. Thread name return getThreadNamePrefix () + enclosing threadCount. IncrementAndGet ();

      The default prefix is return classutils.getShortName (getClass()) + “-“;

    4. corePoolSize=1

    5. maximumPoolSize = MAX_VALUE

    6. ttl=0

    7. Queue =new DelayedWorkQueue(), // This is xecutor’s default queue

  2. There are two default strategies for exception handling after an Executor execution fails (the ErrorHandler interface is implemented primarily and can also be set using the set method)

    1. LoggingErrorHandler (default)
    2. 5, PropagatingErrorHandler (Type the log, and an error is reported)

The last step is how to run scheduled tasks according to cron expressions and, so far, do not see CronTrigger nextExecutionTime

5. How to do scheduling (ReschedulingRunnable concrete implementation)

First, it is a ScheduledFuture. The main focus here is on the two methods run and Schedule, which are cleverly implemented.

  • The schedule method

    @Nullable public ScheduledFuture<? > the schedule () {synchronized (enclosing triggerContextMonitor) {/ / get the next task execution events this. ScheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext); if (this.scheduledExecutionTime == null) { return null; } / / calculate the need to delay start event long initialDelay = this. ScheduledExecutionTime. GetTime () - System. CurrentTimeMillis (); CurrentFuture = this.executor.schedule(this, initialDelay, timeunit.milliseconds); return this; }}Copy the code
  • Run method

    @Override
    	public void run(a) {
    		Date actualExecutionTime = new Date();
    		// Start running the task, which is called by reflection,
    		// ScheduledMethodRunnable (runnable, ScheduledMethodRunnable, runnable, ScheduledMethodRunnable
    		// There is also exception handling after execution failure
    		super.run();
    
    		Date completionTime = new Date();
    		synchronized (this.triggerContextMonitor) {
    			//scheduledExecutionTime This is not null because scheduledExecutionTime is called first.
    			Assert.state(this.scheduledExecutionTime ! =null."No scheduled execution");
    			// Update triggerContext. TriggerContext is directly new in this class
    			// Last execution time, the actual time spent on the last execution, the time to complete the task
    			this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
    			if(! obtainCurrentFuture().isCancelled()) {// Continue to call schedule, this is the essence, today's focus. Continue to sign up, continue to cycle,schedule(); }}}Copy the code

    The two methods involved are really subtle, avoiding the problem of spacing and achieving absolute standards of time.

    There is another way to design timed tasks

    1. Parse the cron inside the annotations and maintain it in one place (memory or Redis)

    2. There are two components, the scheduler and the actuator,

      1. The scheduler

        How many seconds does it take to iterate over all tasks to see if the next execution time is smaller than the current time? If it is smaller, it indicates that the execution has been changed, and the method is passed to the executor, which performs the task. The scheduler still performs all tasks at the same interval as before

      2. actuator

        Perform a task

      But there is a problem with this, for example, 10 seconds apart, but the task needs to be executed every 4 seconds, which is a problem

    However, Spring’s approach does not cause such problems, and it also supports cancelling tasks. Because the Future reference is maintained.

Focus on

  1. The relationship between the schedule and run methods in ReschedulingRunnable if there is a task to be executed every 5 seconds.
    1. It starts by calling the Schedule method, which calculates the next execution time, calculates the difference between the current time and the next execution time, and then delays the start to get the exact start time.
    2. The run method will call ScheduledMethodRunnable’s run method, and ScheduledMethodRunnable will call the method via reflection.
    3. After the execution is complete, the Context is updated to retain information about the execution, and the future is checked to see if it is cancelled. If it is not cancelled, the schedule method is called again.

At this point, we’re done with the implementation of scheduled tasks in Spring. I can’t help but sigh, this implementation is really clever.