Spring task execution and task scheduling

Introduction

The Spring framework provides the abstraction of asynchronous execution and task scheduling through the TaskExecutor and TaskScheduler interfaces, respectively. Spring also provides implementations of those interfaces that support thread pooling or delegation to CommonJ in an application server environment. Ultimately, using these implementations behind the public interface can abstract the differences between Java SE 5, Java SE 6 and Java EE environments.

Spring also has an integration class to support scheduling with Timer (part of the JDK since 1.3) and Quartz Scheduler ( https://www.quartz-scheduler.org/). Both schedulers are set using FactoryBean, with optional references to Timer or Trigger instances, respectively. In addition, Quartz Scheduler and Timer convenience classes are also provided, which allows you to call methods of existing target objects (similar to regular MethodInvokingFactoryBean operations).

Spring TaskExecutor abstraction

Executors is the JDK name of the thread pool concept. The name of "executor" is because there is no guarantee that the underlying implementation is actually a pool; the executor may be single-threaded or even synchronized. Spring's abstraction hides the implementation details between Java SE and Java EE environments

Spring's TaskExecutor interface is the same as the java.util.concurrent.Executor interface. In fact, initially, the main reason for its existence was to abstract the need for Java 5 when using the thread pool. The interface has a single method execute (Runnable task), which accepts the task to be executed according to the semantics and configuration of the thread pool.

TaskExecutor was originally created to provide the thread pool abstraction needed for other Spring components. ApplicationEventMulticaster, JMS AbstractMessageListenerContainer and Quartz integration and other components all use TaskExecutor abstraction to pool threads. However, if your beans require thread pool behavior, you can use this abstraction according to your needs.

TaskExecutor type

The Spring distribution contains many pre-built implementations of TaskExecutor. You will most likely not need to implement your own method. Common ready-to-use variants are:

  • SyncTaskExecutor This implementation does not execute calls asynchronously. Instead, each call is made in the calling thread. It is mainly used in situations where multiple threads are not required, for example in simple test cases.
  • SimpleAsyncTaskExecutor This implementation does not reuse any threads, but starts a new thread for each call. However, it does support the concurrency limit, which will prevent all calls that exceed the limit until the slot is released. If you are looking for a real pool, please see ThreadPoolTaskExecutor below.
  • ConcurrentTaskExecutor This implementation is an adapter for java.util.concurrent.Executor instances. There is also an alternative method, ThreadPoolTaskExecutor, which exposes Executor configuration parameters as bean properties. It is rarely necessary to use ConcurrentTaskExecutor directly, but if ThreadPoolTaskExecutor is not flexible enough to meet your needs, you can choose ConcurrentTaskExecutor.
  • This implementation of ThreadPoolTaskExecutor is the most commonly used implementation . It exposes the bean properties used to configure java.util.concurrent.ThreadPoolExecutor and wraps it in TaskExecutor. If you need to adapt to other types of java.util.concurrent.Executor, it is recommended that you use ConcurrentTaskExecutor instead.
  • WorkManagerTaskExecutor This implementation uses CommonJ WorkManager as its supporting service provider, and is a central convenience class that sets up a CommonJ-based thread pool integration on WebLogic / WebSphere in the context of a Spring application.
  • DefaultManagedTaskExecutor This implementation uses the ManagedExecutorService obtained by JNDI in a JSR-236 compatible runtime environment (such as Java EE 7+ application server), which replaces CommonJ WorkManager.

Use TaskExecutor

Spring's TaskExecutor implementation is used as a simple JavaBean. In the following example, we define a bean that uses ThreadPoolTaskExecutor to print out a set of messages asynchronously.

import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.TaskExecutor;

/**
 * @author Created by niugang on 2020/4/7/20:46
 */
public class TaskExecutorExample implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        printMessages();
    }

    private class MessagePrinterTask implements Runnable {

        private String message;

        public MessagePrinterTask(String message) {
            this.message = message;
        }


        @Override
        public void run() {
            System.out.println(message);
        }
    }

    private TaskExecutor taskExecutor;

    public TaskExecutorExample(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }


    public void printMessages() {
        for (int i = 0; i < 25; i++) {
            taskExecutor.execute(new MessagePrinterTask("Message" + i));
        }
    }
}

As you can see, you can add Runnable to the queue, and TaskExecutor uses its internal rules to determine when to execute the task, instead of retrieving threads from the pool and executing itself.

In order to configure the rules that TaskExecutor will use, simple bean properties have been exposed.

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="5"/>
        <property name="maxPoolSize" value="10"/>
        <property name="queueCapacity" value="25"/>
    </bean>

    <bean id="taskExecutorExample" class="com.xdja.dsc.system.springvalidate.task.TaskExecutorExample">
        <constructor-arg ref="taskExecutor"/>
    </bean>

Java config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author Created by niugang on 2020/4/8/14:41
 */
@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        threadPool.setMaxPoolSize(10);
        threadPool.setCorePoolSize(2);
        threadPool.setQueueCapacity(100);
        return  threadPool;
    }

}
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;


/**
 * @author Created by niugang on 2020/4/7/20:46
 */
@Component
public class TaskExecutorExample implements InitializingBean {

    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    public TaskExecutorExample(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        printMessages();
    }

    private class MessagePrinterTask implements Runnable {

        private String message;

        public MessagePrinterTask(String message) {
            this.message = message;
        }


        @Override
        public void run() {
            System.out.println(message);
        }
    }


    public void printMessages() {
        for (int i = 0; i < 25; i++) {
            threadPoolTaskExecutor.execute(new MessagePrinterTask("Message" + i));
        }
    }
}

Spring TaskScheduler抽象

In addition to the TaskExecutor abstraction, Spring 3.0 also introduces TaskScheduler, which has multiple methods for scheduling tasks to run at a certain point in the future.

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

The simplest method is a method called "schedule", which only accepts Runnable and Date. This will cause the task to run once after the specified time. All other methods can schedule tasks to run repeatedly. The fixed rate and fixed delay methods are used for simple periodic execution, but the method of accepting triggers is more flexible.

Trigger interface

The Trigger interface is essentially inspired by JSR-236 (not officially implemented since Spring 3.0). The basic idea of ​​the trigger is to determine the execution time based on past execution results and even arbitrary conditions. If these determinations do take into account the results of previous executions, this information is available in TriggerContext. The Trigger interface itself is very simple:

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

As you can see, TriggerContext is the most important part. It encapsulates all relevant data and opens it up for expansion when necessary in the future. TriggerContext is an interface (implemented using SimpleTriggerContext by default). Here, you can see which methods are available for Trigger implementation.

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

Trigger implementation

Spring provides two implementations of the Trigger interface. The most interesting is CronTrigger. It enables task scheduling based on cron expressions. For example, the following tasks are scheduled to run after 15 minutes per hour, but only during 9 to 17 "working hours" on weekdays.

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

Insert picture description here

Another ready-made implementation is PeriodicTrigger, which accepts a fixed time period, an optional initial delay value, and a Boolean value to indicate whether the time period should be interpreted as a fixed rate or a fixed delay. Since the TaskScheduler interface has defined methods for scheduling tasks at a fixed rate or fixed delay, these methods should be used directly as much as possible. The value of the PeriodicTrigger implementation is that it can be used in components that rely on trigger abstraction. For example, it may be convenient to allow periodic triggers, cron-based triggers, and even custom triggers to be used interchangeably. Such components can take advantage of dependency injection, so that such triggers can be configured externally, so they can be easily modified or extended.

TaskScheduler implementation

Insert picture description here

Like Spring's TaskExecutor abstraction, the main benefit of TaskScheduler arrangement is that the scheduling requirements of the application are separated from the deployment environment. This level of abstraction is particularly important when deploying to an application server environment where threads should not be created directly by the application itself. For such cases, Spring provides a CommonJ TimerManager TimerManagerTaskScheduler delegated to WebLogic / WebSphere, and an updated DefaultManagedTaskScheduler delegated to JSR-236 ManagedScheduledExecutorService in a Java EE 7+ environment, usually configured with JNDI lookup.

Whenever external thread management is not required, a simpler option is to set the local ScheduledExecutorService in the application, which can be adjusted through Spring's ConcurrentTaskScheduler. For convenience, Spring also provides a ThreadPoolTaskScheduler, which is internally delegated to ScheduledExecutorService, so as to provide a common bean style configuration according to the way of ThreadPoolTaskExecutor. These variants also apply to local embedded thread pool settings in a loose application server environment, especially on Tomcat and Jetty

    <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
        <property name="poolSize" value="10"/>
    </bean>

    <bean id="taskScheduleExample" class="com.xdja.dsc.system.springvalidate.task.TaskScheduleExample">
        <constructor-arg ref="taskExecutor"/>
    </bean>
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.Date;


/**
 * @author Created by niugang on 2020/4/7/20:46
 */

public class TaskScheduleExample implements InitializingBean {

   private   ThreadPoolTaskScheduler  threadPoolTaskScheduler;

    public TaskScheduleExample(ThreadPoolTaskScheduler threadPoolTaskScheduler) {
        this.threadPoolTaskScheduler=threadPoolTaskScheduler;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        threadPoolTaskScheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                 //5秒执行一次
                System.out.println("定时任务执行:"+new Date());
            }
        },new Date(),5000);
    }
}

Annotated task planning and task execution

Spring provides annotation support for task scheduling and asynchronous method execution

Enable scheduled task annotation

To enable support for @Scheduled and @Async annotations, add @EnableScheduling and @EnableAsync to one of your @Configuration classes:

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

You are free to choose the relevant notes suitable for your application. For example, if you only need support for @Scheduled, you only need to omit @EnableAsync. For more fine-grained control, you can also implement the SchedulingConfigurer or AsyncConfigurer interface

If you prefer XML configuration, use the <task: annotation-driven> element.

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>

Please note that using the XML above, an executor reference will be provided to handle those tasks corresponding to the method with @Async annotation, and a scheduler reference will be provided to manage those methods annotated with @Scheduled.

The default recommended mode for handling @Async annotations is "proxy", which only allows intercepting calls through a proxy. Local calls in the same class cannot be intercepted in this way. For more advanced listening modes, consider switching to "aspectj" mode in conjunction with compile-time or load-time weaving.

Note: Asynchronous execution cannot be performed in the same class above

@Scheduled annotation

The @Scheduled annotation can be added to the method along with the trigger metadata. For example, the following method will be called every 5 seconds with a fixed delay, which means that the time period will be calculated from the completion time of each previous call.

@Scheduled(fixedDelay=5000)
public void doSomething() {
    // something that should execute periodically
}

If fixed-rate execution is required, simply change the attribute name specified in the comment. Perform the following operations every 5 seconds measured between successive start times of each call.

@Scheduled(fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

For fixed delay and fixed rate tasks, you can specify an initial delay to indicate the number of milliseconds to wait before executing the method for the first time.

@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

If simple periodic scheduling is not sufficient, cron expressions can be provided. For example, the following is only executed on working days.

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
    // something that should execute on weekdays only
}

Note that the method to be scheduled must have a null return value, and no parameters can be expected. If the method needs to interact with other objects in the application context, these objects are usually provided through dependency injection.

Starting with Spring Framework 4.3, any scope of beans supports @Scheduled method.

Make sure not to initialize multiple instances of the same @Scheduled annotation class at runtime, unless you really want to schedule callbacks for each such instance. Related to this, make sure not to use @Configurable on bean classes that are annotated with @Scheduled and registered as regular Spring Beans through the container: otherwise, you will get double initialization, once through the container, once through the @Configurable aspect, each @Scheduled The method will be called twice.

@Async annotation

An @Async annotation can be provided on the method so that calls to the method will occur asynchronously. In other words, the caller will return immediately after the call, and the actual execution of the method will occur in the task that has been submitted to the Spring TaskExecutor. In the simplest case, you can apply annotations to methods that have no return value.

@Async
void doSomething() {
    // this will be executed asynchronously
}

Unlike the methods annotated with @Scheduled, these methods can use parameters because they will be called by the caller in a "regular" manner at runtime, rather than from a container-managed scheduled task. For example, the following is a legitimate application annotated by @Async.

@Async
void doSomething(String s) {
    // this will be executed asynchronously
}

The @Async method can declare not only the regular java.util.concurrent.Future return type, but also Spring's org.springframework.util.concurrent.ListenableFuture, or as of Spring 4.2, JDK 8's java.util.concurrent.CompletableFuture : To achieve richer interaction and asynchronous tasks and immediately synthesized through further processing steps.

Asynchronous instance:

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * @author Created by niugang on 2020/4/8/16:48
 */
@Configuration
/**
 * 处理@Async批注的默认建议模式是“ proxy”,它仅允许通过代理来拦截呼叫。
 * 同一类中的本地调用无法以这种方式被拦截。
 * 对于更高级的侦听模式,请考虑结合编译时或加载时编织切换到“ aspectj”模式
 */
@EnableAsync
public class AppAsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return executor;
    }
}

controller

   @GetMapping("async")
    public Object findByCodeAndAuthor() {
        asyncService.asyncMethod();
        return "success";

    }

    @GetMapping("async2")
    public Object findByCodeAndAuthor2() {
        asyncService.asyncMethod2();
        return "success";

    }

service

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author Created by niugang on 2020/4/8/17:07
 */
@Service
@Slf4j
public class AsyncService {

    private final ApplicationContext applicationContext;

    @Autowired
    public AsyncService(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }


    @Async
    public void asyncMethod() {


        log.info("开始执行异步方法:{},Thread Name:{}", new Date(), Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(5);
            log.info("异步方法执行结束:{}", new Date());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

    }


    /**
     * 解决同类方法调用
     */
    public void asyncMethod2() {
        AsyncService asyncService = applicationContext.getBean(AsyncService.class);
        asyncService.asyncMethod3();

    }

    @Async
    void asyncMethod3() {

        log.info("开始执行异步本类方法:{},Thread Name:{}", new Date(), Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(5);
            log.info("异步方法执行本类结束:{}", new Date());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

    }
}

@ Async's exception management

When the @Async method has a return value of type Future, it is easy to manage the exception that is thrown during method execution, because this exception is thrown when the Future result is called. However, for void return types, this exception will not be caught and cannot be transmitted. For these situations, you can provide AsyncUncaughtExceptionHandler to handle such exceptions.

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

By default, only exceptions are logged. AsyncUncaughtExceptionHandler can be defined through AsyncConfigurer or task: annotation-driven XML elements.

Insert picture description here

Guess you like

Origin www.cnblogs.com/niugang0920/p/12689207.html