How to pass context in Spring asynchronous call

The following article comes from aoho quest  , author aoho

 

1. What is an asynchronous call?

  Asynchronous calls are synchronous calls relative terms, refers to synchronous call is a step by step procedure to perform a predetermined sequence, each step must wait until after the implementation of the previous step to perform, without waiting for the asynchronous call is executing the program step can be executed. Asynchronous call means that you can continue to implement the code behind the program is executed, the return value without waiting for execution. In our application services, there are a lot of business logic does not need to perform the operation returned synchronously (such as sending e-mail, redundant data sheets, etc.), you only need to execute asynchronously.

  This article will introduce Spring applications, how to achieve asynchronous calls. During the asynchronous calls, the thread context information is lost there will be, how do we solve the delivery thread context information.

 

2. Spring asynchronous applications

  Spring is the task scheduling and asynchronous method execution provides annotation support. By providing @Async annotation on the class or method, such a method can be invoked asynchronously. The caller returns immediately when you call, but the actual implementation of the method is to be called the Spring TaskExecutor to complete. So when the annotated method is called, will be executed in the new thread, and call its methods will perform in the original thread to avoid blocking, and ensure real-time tasks.

2.1  introduced dependence

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

  Spring related to the introduction of dependency. After Spring5.0, Spring Spring Boot official recommended to build the project.

 

2.2 class inlet

@SpringBootApplication
@EnableAsync
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

  Inlet class increased @EnableAsync annotation, all the main scanning range for the package under @Async annotations.

 

2.3 external interface

  Here wrote a simple interface:

@RestController
@ Slf4j
public  class TaskController {

    @Autowired
    private TaskService taskService;

    @GetMapping("/task")
    public String taskExecute() {
        try {
            taskService.doTaskOne();
            taskService.doTaskTwo();
            taskService.doTaskThree();
        } catch (Exception e) {
           log.error("error executing task for {}",e.getMessage());
        }
        return "ok";
    }
}

  Call TaskService perform three asynchronous method.

 

2.4 Service Methods

@Component
@Slf4j
public class TaskService {

    @Async
    public void doTaskOne() throws Exception {
        log.info ( "start doing a task" );
         Long Start = System.currentTimeMillis ();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        log.info ( "a task, Processed:" + (end - start) + " msec" );
    }

    @Async
    public void doTaskTwo() throws Exception {
        log.info ( "started Task II" );
         Long Start = System.currentTimeMillis ();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        log.info ( "two tasks, Processed:" + (end - start) + " msec" );
    }

    @Async
    public void doTaskThree() throws Exception {
        log.info ( "started Task Three" );
         Long Start = System.currentTimeMillis ();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        log.info ( "task III Processed:" + (end - start) + " msec" );
    }
}

  @Async may be used in the class, all the way to identify such methods are asynchronous, certain methods may be used separately. Each method will sleep 1000 ms.

 

2.5 The results show

  Results are as follows:

 

 

  See TaskService three methods are executed asynchronously, the results interface to return fast, asynchronous output log information. Asynchronous call, by opening a new thread calls the method does not affect the main thread. The actual execution of asynchronous method to Spring TaskExecutor to complete.

 

3. Future: Getting Results asynchronous execution

  In the above tests, we can also find the main method call did not wait for executing the method call on the end of the current task. If you want to know all three of executing the method call how to do it, we're ready to use asynchronous callbacks.

  Asynchronous callback method is called is to let each return a Future value type, Spring provides a subclass Future interface: AsyncResult , so we can return the value AsyncResult type.

public class AsyncResult<V> implements ListenableFuture<V> {

    private final V value;

    private final ExecutionException executionException;
    //...
}

  AsyncResult achieved ListenableFuture interface inside the object has two attributes: return value and exception information.

public interface ListenableFuture<T> extends Future<T> {
    void addCallback(ListenableFutureCallback<? super T> var1);

    void addCallback(SuccessCallback<? super T> var1, FailureCallback var2);
}

  ListenableFuture interface inherits from the Future, based on the increase in this defined callback method. Future interface is defined as follows:

public  interface Future <V> {
     // if the currently executing task can be interrupted in 
    Boolean Cancel ( Boolean the mayInterruptIfRunning);

    // task to cancel the results of 
    boolean isCancelled ();

    // the value of the object that the asynchronous method returns the last 
    V GET () throws InterruptedException, ExecutionException;
     // to determine whether the asynchronous tasks is completed, if the execution is completed, it returns true, if not completed execution, false is returned 
    boolean isDone ();
     // Like get (), except that here the parameters set timeout 
    V GET ( Long timeout, TimeUnit Unit)
         throws InterruptedException, ExecutionException, TimeoutException;
}

  #get () method, in the implementation is the need to wait for the callback result, blocking wait. If you do not set a timeout period, it is blocked until the task has completed execution there. We set the timeout, it can interrupt the current task execution in the case of the current task for too long, the release of the thread, so it will not lead to tie up resources.

  #cancel (boolean) method, the value of the parameter is a boolean type that can be interrupted if incoming tasks currently being executed. If the argument is true and the current task is not executed, the instructions may interrupt the current task, it will return true; if the current task has not been executed, regardless of the parameter is true or false, the return value is true; if the current task has been completed , then regardless of the argument is true or false, the return value is false; if the current task is not completed and the argument is false, then the return value is false. which is:

  • If the task is not executed, if you want to cancel the task, it will return true, regardless of the parameters.
  • If the task has been executed, then the task must be irrevocable, so in this case the return value is false, regardless of the parameters.
  • If the task is being performed, then at this time whether to cancel the task is to see whether the parameters are allowed to interrupt (true / false).

 

3.1 acquiring asynchronous method returns a value of

public Future<String> doTaskOne() throws Exception {
    log.info ( "start doing a task" );
     Long Start = System.currentTimeMillis ();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info ( "a task, Processed:" + (end - start) + " msec" );
     return  new new the AsyncResult <> ( "task is completed, time-consuming" + (end - start) + " ms" ) ;
}
// ... similar to the other two methods, omitted

  We will return value to the method of task Future <String> , the execution time for the splicing string.

@GetMapping("/task")
public String taskExecute() {
    try {
        Future<String> r1 = taskService.doTaskOne();
        Future<String> r2 = taskService.doTaskTwo();
        Future<String> r3 = taskService.doTaskThree();
        while (true) {
            if (r1.isDone() && r2.isDone() && r3.isDone()) {
               log.info("execute all tasks");
               break;
            }
            Thread.sleep(200);
        }
        log.info("\n" + r1.get() + "\n" + r2.get() + "\n" + r3.get());
    } catch (Exception e) {
        log.error("error executing task for {}",e.getMessage());
    }
    return "ok";
}

 

 

   After calling the asynchronous method, the cycle can be accomplished by determining whether to perform the asynchronous method. As the result we expected, future is to get the string AsyncResult returned.

 

4. Configure thread pool

  Front is the easiest to use, use the default TaskExecutor . If you want to use a custom Executor, can be combined @Configuration configuration annotations.

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

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class TaskPoolConfig {

    @Bean ( "taskExecutor") // name of the bean, the default method name in lowercase letters, led by 
    public Executor taskExecutor () {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize ( 10); // core threads (the default number of threads) 
        executor.setMaxPoolSize (20 is); // Maximum number of threads 
        executor.setQueueCapacity (200 is); // buffer queue number 
        executor.setKeepAliveSeconds (60); // allowing threads to idle time (unit: seconds by default) 
        executor.setThreadNamePrefix ( "taskExecutor-"); // thread pool name prefix
         // thread pool to reject the task of processing strategies 
        executor.setRejectedExecutionHandler ( new new ThreadPoolExecutor.CallerRunsPolicy ());
         return executor;
    }
}

  The thread pool configuration is very flexible, the core number of threads, the maximum number of threads and other attributes to be configured. Which, rejection-policy, when the thread pool has reached the maximum number of threads, how to handle new tasks. Optional strategies CallerBlocksPolicy, CallerRunsPolicy and so on. CALLER_RUNS: not in the new thread to perform the task, but performed by the thread of the caller is located. With our verification, set the thread pool is in effect, in TaskService, the thread Print name:

public Future<String> doTaskOne() throws Exception {
    log.info ( "start doing a task" );
     Long Start = System.currentTimeMillis ();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info ( "a task, Processed:" + (end - start) + " msec" );
    log.info ( "current thread} {" , Thread.currentThread () getName ().);
     return  new new the AsyncResult <> ( "task is completed, time-consuming" + (end - start) + " msec" );
}

 

 

 

  You can see by the results, the thread pool thread name prefix configuration has taken effect. In the Spring @Async asynchronous thread course, it is noted that the following usage will @Async failure:

  • Asynchronous method using a static modification;
  • Asynchronous class does not use @Component notes (or other notes) can not be scanned to lead Spring asynchronous class;
  • The method of asynchronous method is called an asynchronous and not in the same class;
  • Class requires the use of automatic annotation @Resource or the like @Autowired injection, new objects can not manually;
  • If you are using Spring Boot framework must be increased in the startup class @EnableAsync comment.

 

The  thread context information transfer

  In many cases, micro-services architecture first request involves multiple micro-services. There will be a service or a plurality of processing methods that might be asynchronous method. Some thread context information, such as the request path, only userId user, this information would have been passed in the request. Without doing anything, we look at whether the normal access to such information.

@GetMapping("/task")
public String taskExecute() {
    try {
        Future<String> r1 = taskService.doTaskOne();
        Future<String> r2 = taskService.doTaskTwo();
        Future<String> r3 = taskService.doTaskThree();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        log.info ( "current thread is {} {} method request, path request: {}" ., Thread.currentThread () getName (), request.getMethod (), 
          . request.getRequestURL () toString () );
the while ( to true ) { IF (r1.isDone () && r2.isDone () && r3.isDone ()) { log.info("execute all tasks"); break; } Thread.sleep(200); } log.info("\n" + r1.get() + "\n" + r2.get() + "\n" + r3.get()); } catch (Exception e) { log.error("error executing task for {}", e.getMessage()); } return "ok"; }

  In Spring Boot Web, we can RequestContextHolder easily get request. In the interface method, the method of outputting the request and the path request.

public Future<String> doTaskOne() throws Exception {
    log.info ( "start doing a task" );
     Long Start = System.currentTimeMillis ();
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    log.info ( "a task, Processed:" + (end - start) + " msec" );
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = requestAttributes.getRequest();
    log.info ( "current thread is {} {} method request, path request: {}" ., Thread.currentThread () getName (), request.getMethod (), 
      . request.getRequestURL () toString () );
return new new the AsyncResult <> ( "task is completed, time-consuming" + (end - start) + " msec" ); }

  In the same time TaskService, authentication information can not output request. Run the program, results are as follows:

 

 

   In TaskService, acquisition method of each asynchronous thread RequestContextHolder request information, the null pointer exception reported. This shows the context information request is not transmitted to the method of the asynchronous threads. RequestContextHolder implementation, there are two ThreadLocal saved request under the current thread.

//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");

  再看 #getRequestAttributes() 方法,相当于直接获取 ThreadLocal 里面的值,这样就使得每一次获取到的 Request 是该请求的 request。如何将上下文信息传递到异步线程呢?Spring 中的 ThreadPoolTaskExecutor 有一个配置属性 TaskDecorator,TaskDecorator 是一个回调接口,采用装饰器模式。装饰模式是动态的给一个对象添加一些额外的功能,就增加功能来说,装饰模式比生成子类更为灵活。因此 TaskDecorator 主要用于任务的调用时设置一些执行上下文,或者为任务执行提供一些监视/统计

public interface TaskDecorator {

    Runnable decorate(Runnable runnable);
}

  #decorate 方法,装饰给定的 Runnable,返回包装的 Runnable 以供实际执行。

  下面我们定义一个线程上下文拷贝的 TaskDecorator

import org.springframework.core.task.TaskDecorator;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class ContextDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

  实现较为简单,将当前线程的 context 装饰到指定的 Runnable,最后重置当前线程上下文。

  在线程池的配置中,增加回调的 TaskDecorator 属性的配置:

@Bean("taskExecutor")
public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(20);
    executor.setQueueCapacity(200);
    executor.setKeepAliveSeconds(60);
    executor.setThreadNamePrefix("taskExecutor-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setAwaitTerminationSeconds(60);
    // 增加 TaskDecorator 属性的配置
    executor.setTaskDecorator(new ContextDecorator());
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

  经过如上配置,我们再次运行服务,并访问接口,控制台日志信息如下:

 

   由结果可知,线程的上下文信息传递成功。

 

小结

  本文结合示例讲解了 Spring 中实现异步方法,获取异步方法的返回值。并介绍了配置 Spring 线程池的方式。最后介绍如何在异步多线程中传递线程上下文信息。线程上下文传递在分布式环境中会经常用到,比如分布式链路追踪中需要一次请求涉及到的 TraceId、SpanId。简单来说,需要传递的信息能够在不同线程中。异步方法是我们在日常开发中用来多线程处理业务逻辑,这些业务逻辑不需要严格的执行顺序。用好异步解决问题的同时,更要用对异步多线程的方式。

Guess you like

Origin www.cnblogs.com/huanshilang/p/12200447.html