Summary of Spring @Async usage

Posted by Ell20 on Sat, 22 Jan 2022 11:05:09 +0100

stay Java In most applications, interactive processing is realized by means of synchronization; However, when dealing with the interaction with third-party systems, it is easy to cause slow response. Before, most of them used multithreading to complete such tasks. In fact, in spring  3. After X, @ Async has been built in to perfectly solve this problem. This article will complete the introduction to the usage of @ Async.

1. What is asynchronous call?

Before explaining asynchronous invocation, let's look at the definition of synchronous invocation; Synchronization is the sequential execution of the whole processing process. When each process is completed, the results are returned. Asynchronous calling is just sending the called instruction, and the caller does not need to wait for the called method to be fully executed; Instead, continue with the following process.

For example, in a call, you need to call three process methods a, B and C in sequence; If they are all synchronous calls, they need to be executed in sequence before the process is completed; If B is an asynchronous calling method, after calling A after executing the B, it does not wait for B to complete. Instead, the execution starts calling C. After C is executed, it means that the process is finished.

2. Conventional asynchronous call processing method

In Java, when dealing with similar scenarios, it is generally based on creating an independent thread to complete the corresponding asynchronous call logic, through the execution process between the main thread and different threads, so that after the independent thread is started, the main thread continues to execute without stagnation.

3. @Async introduction

In Spring, the method based on @ Async annotation is called asynchronous method; When these methods are executed, they will be executed in a separate thread. The caller can continue other operations without waiting for it to complete.

Note: the function modified by @ Async should not be defined as static type, so the asynchronous call will not take effect, and the following error will be reported:

From the exception information JedisConnectionException: Could not get a resource from the pool, we can easily imagine that the asynchronous task was still executing when the application was closed. Because the Redis connection pool was destroyed first, the above error was reported for the operation to access Redis in the asynchronous task. Therefore, we conclude that the above implementation method is not elegant when the application is closed, so what should we do? The settings are as follows:
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);

How to enable @ Async in Spring

1. Enabling method based on Java configuration:

@Configuration  
@EnableAsync  
public class SpringAsyncConfig { ... }

The configuration in springboot is:

@EnableSwagger2
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {

    public static void main(String[] args) {
        
        SpringApplication.run(SettlementApplication.class, args);
    }
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
}

2. The enabling method based on XML configuration file is configured as follows:

<task:executor id="myexecutor" pool-size="5"  />  
<task:annotation-driven executor="myexecutor"/>  

These are two ways to define.

4. No return value call based on @ Async

Examples are as follows:

@Async  //Annotation use  
public void asyncMethodWithVoidReturnType() {  
    System.out.println("Execute method asynchronously. "  
      + Thread.currentThread().getName());  
}  

The method of use is very simple. One annotation can solve all problems.

5. Call based on @ Async return value

Examples are as follows:

@Async  
public Future<String> asyncMethodWithReturnType() {  
    System.out.println("Execute method asynchronously - "  
      + Thread.currentThread().getName());  
    try {  
        Thread.sleep(5000);  
        return new AsyncResult<String>("hello world !!!!");  
    } catch (InterruptedException e) {  
        //  
    }  
   
    return null;  
}   

The above example shows that the returned data type is Future, which is an interface. The specific result type is AsyncResult, which needs attention.

Example of calling an asynchronous method that returns a result:

public void testAsyncAnnotationForMethodsWithReturnType()  
   throws InterruptedException, ExecutionException {  
    System.out.println("Invoking an asynchronous method. "  
      + Thread.currentThread().getName());  
    Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();  
   
    while (true) {  ///Loop judgment is used here to wait for the result information  
        if (future.isDone()) {  //Judge whether the execution is completed  
            System.out.println("Result from asynchronous process - " + future.get());  
            break;  
        }  
        System.out.println("Continue doing something else. ");  
        Thread.sleep(1000);  
    }  
}  

Analysis: the result information of these asynchronous methods is obtained by constantly checking the status of Future to obtain whether the current asynchronous method has completed execution.

6. Based on the exception handling mechanism in @ Async call

In an asynchronous method, if an exception occurs, it is imperceptible to the caller caller.

Take an example:

A. Asynchronous method class:

/**
 * @author duanxz
 * 2018 August 2, 2014 4:36:49 PM
 */
@Component
public class AsyncServiceTest {

    @Async
    public void test() {
        System.out.println("AsyncServiceTest.test()");
        throw new IllegalArgumentException("sssssssssssssssssss");
    }
}

B. Business call class

//...
test.test();
System.out.println("channelUploadFileList()" + LocalDateTime.now());
MLogModel model = new MLogModel();
//...

Look at the results:

If you really need to handle exceptions, follow the following methods:

    1. Customize the task executor that implements asynctask executor, and define the logic and method of handling specific exceptions here.

    2. Configure a custom TaskExecutor to replace the built-in task executor

Example step 1, custom TaskExecutor

public class ExceptionHandlingAsyncTaskExecutor implements AsyncTaskExecutor {  
    private AsyncTaskExecutor executor;  
    public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {  
        this.executor = executor;  
     }  
      Wrapped in separate threads,@Async This is its essence  
    public void execute(Runnable task) {       
      executor.execute(createWrappedRunnable(task));  
    }  
    public void execute(Runnable task, long startTimeout) {  
        /Wrapped in separate threads,@Async This is its essence  
       executor.execute(createWrappedRunnable(task), startTimeout);           
    }   
    public Future submit(Runnable task) { return executor.submit(createWrappedRunnable(task));  
       //Wrapped in separate threads, @ Async is its essence.  
    }   
    public Future submit(final Callable task) {  
      //Wrapped in separate threads, @ Async is its essence.  
       return executor.submit(createCallable(task));   
    }   
      
    private Callable createCallable(final Callable task) {   
        return new Callable() {   
            public T call() throws Exception {   
                 try {   
                     return task.call();   
                 } catch (Exception ex) {   
                     handle(ex);   
                     throw ex;   
                   }   
                 }   
        };   
    }  
  
    private Runnable createWrappedRunnable(final Runnable task) {   
         return new Runnable() {   
             public void run() {   
                 try {  
                     task.run();   
                  } catch (Exception ex) {   
                     handle(ex);   
                   }   
            }  
        };   
    }   
    private void handle(Exception ex) {  
      //Where specific exception logic is handled  
      System.err.println("Error during @Async execution: " + ex);  
    }  
} 

Analysis: it can be found that it implements the asynctask executor, which uses independent threads to execute each specific method operation. In createCallable and createWrapperRunnable, the exception handling method and mechanism are defined.

handle() is where we need to focus on exception handling in the future.

Content in configuration file:

<task:annotation-driven executor="exceptionHandlingTaskExecutor" scheduler="defaultTaskScheduler" />  
    <bean id="exceptionHandlingTaskExecutor" class="nl.jborsje.blog.examples.ExceptionHandlingAsyncTaskExecutor">  
        <constructor-arg ref="defaultTaskExecutor" />  
    </bean>  
    <task:executor id="defaultTaskExecutor" pool-size="5" />  
    <task:scheduler id="defaultTaskScheduler" pool-size="1" />

Analysis: the configuration here uses a custom taskexecutor to replace the default taskexecutor.

Or (look at the source code of Aysnc first):

public interface AsyncConfigurer {  
    Executor getAsyncExecutor();  
    AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler();  
} 

AsyncConfigurerSupport is an implementation of the AsyncConfigurer interface, but it does nothing.

public class AsyncConfigurerSupport implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return null;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }

}
@Configuration  
@EnableAsync  
class SpringAsyncConfigurer extends AsyncConfigurerSupport {  
  
    @Bean  
    public ThreadPoolTaskExecutor asyncExecutor() {  
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();  
        threadPool.setCorePoolSize(3);  
        threadPool.setMaxPoolSize(3);  
        threadPool.setWaitForTasksToCompleteOnShutdown(true);  
        threadPool.setAwaitTerminationSeconds(60 * 15);  
        return threadPool;  
    }  
  
    @Override  
    public Executor getAsyncExecutor() {  
        return asyncExecutor;  
    }  
} 

You can implement the AsyncConfigurer interface to handle exceptions

@Configuration  
    @EnableAsync  
    public class SpringAsyncConfigurer implements AsyncConfigurer {  
           
        @Override  
        public Executor getAsyncExecutor() {  
            return new ThreadPoolTaskExecutor();  
        }  
      
        @Override  
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {  
            return new CustomAsyncExceptionHandler();  
        }  
      
    }  

Exception handling class:

public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {  
       
        @Override  
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {  
            System.out.println("Exception message - " + throwable.getMessage());  
            System.out.println("Method name - " + method.getName());  
            for (Object param : obj) {  
                System.out.println("Parameter value - " + param);  
            }  
        }  
           
    }  

7. Transaction processing mechanism in @ async call

The @ Async annotation method is also applicable to @ Transactional annotation; When it calls the database operation, it will not be able to generate the control of transaction management, because it is an operation based on asynchronous processing.

How do you add transaction management to these operations? You can put the method that needs transaction management operation inside the asynchronous method, and add @ Transactional. XML to the internally called method

For example, method A uses @ Async/@Transactional to label, but cannot produce the purpose of transaction control.

Method B is marked by @Async, C and D are invoked in B, and @Transactional is annotated by C/D respectively, so that the purpose of transaction control can be realized.

8. Definition of asynchronous thread pool

8.1. A thread pool

@Configuration  
    @EnableAsync  
    public class SpringAsyncConfig {  
        @Bean  
        public AsyncTaskExecutor taskExecutor() {  
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
            executor.setMaxPoolSize(10);  
            return executor;  
        }  
    }  

8.2. Multiple thread pools

@Configuration  
    @EnableAsync  
    public class SpringAsyncConfig {  
           
        @Bean(name = "threadPoolTaskExecutor1")  
        public Executor threadPoolTaskExecutor() {  
            return new ThreadPoolTaskExecutor();  
        }  
           
        @Bean(name = "threadPoolTaskExecutor2")  
        public Executor threadPoolTaskExecutor() {  
            return new ThreadPoolTaskExecutor();  
        }  
      
    }  

call

@Async("threadPoolTaskExecutor1")  
public void asyncMethodWithConfiguredExecutor() {  
    System.out.println("Execute method with configured executor - "  
      + Thread.currentThread().getName());  
}

9. Summary

Through the above description, the methods and precautions used by @ Async should be

Topics: Spring