@Async sends SMS asynchronously

Posted by crazy_carl on Thu, 16 Dec 2021 02:36:24 +0100

preface

Recently, in the order module, users need to make an appointment after purchasing service products. After the appointment is successful, they send reminder messages to merchants and users respectively. Considering the time-consuming situation of sending text messages, I wanted to use asynchronous methods to execute them, so I saw Spring's @ Async on the Internet.

However, many problems have been encountered, which makes @ Async invalid, and no good article has been found to explain in detail the correct and wrong use methods of @ Async and the points needing attention. Here we briefly sort out the problems encountered. Sring starts @ Async in the form of configuration file, while SpringBoot starts @ Async in the form of annotation.

We can use the default thread pool of springBoot, but generally we will customize the thread pool (because it is more flexible). The configuration methods are as follows:

  1. How to configure using xml files

  2. Use Java code and @ Configuration for Configuration (recommended)

The following two configuration methods are implemented respectively

Step 1: configure @ Async

1, Configuration of springBoot startup class:

Configure @ EnableAsync in the main program of Spring Boot as follows:

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

2, Configuration method of Spring XML:

1.applicationContext. Create the file threadPool.xml in the same directory XML file:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:task="http://www.springframework.org/schema/task"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
                        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">  
  
    <!-- Turn on asynchrony and introduce thread pool -->  
    <task:annotation-driven executor="threadPool" />  
  
    <!-- Define thread pool -->  
    <bean id="threadPool"  
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  
        <!-- Number of core threads. The default value is 1 -->  
        <property name="corePoolSize" value="10" />  
  
        <!-- Maximum number of threads, default to Integer.MAX_VALUE -->  
        <property name="maxPoolSize" value="50" />  
  
        <!-- The maximum length of the queue, which generally needs to be set>=notifyScheduledMainExecutor.maxNum;Default to Integer.MAX_VALUE -->  
        <property name="queueCapacity" value="100" />  
  
        <!-- The thread pool maintains the idle time allowed for threads. The default value is 60 s -->  
        <property name="keepAliveSeconds" value="30" />  
  
        <!-- Close automatically after completing the task , Default to false-->  
        <property name="waitForTasksToCompleteOnShutdown" value="true" />  
  
        <!-- The core thread timed out and exited. The default value is false -->  
        <property name="allowCoreThreadTimeOut" value="true" />  
  
        <!-- The processing strategy of thread pool for rejecting tasks (no threads are available) is currently only supported AbortPolicy,CallerRunsPolicy;Default to the latter -->  
        <property name="rejectedExecutionHandler">  
            <!-- AbortPolicy:Direct throw java.util.concurrent.RejectedExecutionException abnormal -->  
            <!-- CallerRunsPolicy:The main thread directly executes the task. After execution, it tries to add the next task to the thread pool, which can effectively reduce the speed of adding tasks to the thread pool -->  
            <!-- DiscardOldestPolicy:Discard old tasks and do not support them temporarily; The discarded task cannot be executed again -->  
            <!-- DiscardPolicy:Discard the current task and do not support it temporarily; The discarded task cannot be executed again -->  
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />  
        </property>  
    </bean>  
</beans>  

2. Then in ApplicationContext Introducing ThreadPool. XML xml: <import resource="threadPool.xml" />

<!--If you do not use a custom thread pool, you can directly use the following label-->  
<!--   
<task:executor id="WhifExecutor" pool-size="10"/>   
-->  
<import resource="threadPool.xml" />  
<task:annotation-driven executor="WhifExecutor" />  

Recommend your own actual Spring Cloud project:

https://github.com/YunaiV/onemall

Step 2: create classes of two asynchronous methods, as shown below:

The first class (this simulates sending SMS after canceling an order. There are two methods to send SMS):

@Service  
public class TranTest2Service {  
  
  
    //Send reminder SMS {1
    @Async  
    public void sendMessage1() throws InterruptedException {  
  
        System.out.println("Sending SMS method---- 1   Execution start");  
        Thread.sleep(5000); //Simulation time
        System.out.println("Sending SMS method---- 1   end of execution");  
    }  
  
    //Send reminder SMS} 2
    @Async  
    public void sendMessage2() throws InterruptedException {  
  
        System.out.println("Sending SMS method---- 2   Execution start");  
        Thread.sleep(2000); //Simulation time
        System.out.println("Sending SMS method---- 2   end of execution");  
    }  
}  

The second class. Call the method of sending SMS (the asynchronous method cannot be in the same class as the called asynchronous method, otherwise it is invalid):

@Service  
public class OrderTaskServic {  
    @Autowired  
    private TranTest2Service tranTest2Service;  
  
    //Order processing task
    public void orderTask() throws InterruptedException {  
  
        this.cancelOrder(); //Cancel order
        tranTest2Service.sendMessage1(); //Methods of sending text messages 1
        tranTest2Service.sendMessage2(); //Methods of sending text messages 2
  
    }  
  
    //Cancel order
    public void cancelOrder() throws InterruptedException {  
        System.out.println("Cancel order execution method------start");  
        System.out.println("Cancel order execution method------end ");  
    }  
  
}  

The test results are as follows:

1. Not using @ Async

2. Used @ Async

It can be seen that sending SMS without @ Async is executed synchronously, which means that the first message is sent and then the second message is sent. After the second message is sent successfully, the user will be prompted. This will obviously affect the user experience. Look at the SMS implemented with @ Async, immediately start another thread to execute the second method after executing the first method, Obviously, our processing speed is much faster.

Configuration method using Java code combined with @ configuration annotation (recommended)

1. Create a new configuration class

package com.boot.common.conf;  
  
import java.util.concurrent.ThreadPoolExecutor;  
  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.annotation.EnableAsync;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;  
  
/**  
 * Thread pool configuration
 * @author zhh  
 *  
 */  
@Configuration  
@EnableAsync  
public class ThreadPoolTaskConfig {  
  
/**   
 *   By default, after the thread pool is created, the number of threads in the thread pool is 0. When a task comes, a thread will be created to execute the task
 *    When the number of threads in the thread pool reaches the corePoolSize, the arriving tasks will be placed in the cache queue;   
 *  When the queue is full, continue to create threads. When the number of threads is greater than or equal to maxPoolSize, start using the reject policy to reject
 */  
  
    /** Number of core threads (default threads)*/  
    private static final int corePoolSize = 20;  
    /** Maximum number of threads*/  
    private static final int maxPoolSize = 100;  
    /** Allowed thread idle time (unit: default is seconds)*/  
    private static final int keepAliveTime = 10;  
    /** Buffer queue size*/  
    private static final int queueCapacity = 200;  
    /** Thread pool name prefix*/  
    private static final String threadNamePrefix = "Async-Service-";  
  
    @Bean("taskExecutor") //The name of the bean. The default method name is lowercase
    public ThreadPoolTaskExecutor taskExecutor(){  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(corePoolSize);     
        executor.setMaxPoolSize(maxPoolSize);  
        executor.setQueueCapacity(queueCapacity);  
        executor.setKeepAliveSeconds(keepAliveTime);  
        executor.setThreadNamePrefix(threadNamePrefix);  
  
        //Processing strategy of thread pool for rejecting tasks
        // CallerRunsPolicy: the calling thread (the thread submitting the task) handles the task
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  
        //Initialization
        executor.initialize();  
        return executor;  
    }  
}  

2. Create classes of two asynchronous methods (similar to the previous classes, but with different method annotations), as follows:

The first class (this simulates sending SMS after canceling an order. There are two methods to send SMS):

package com.boot.test1.service;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.scheduling.annotation.Async;  
import org.springframework.stereotype.Service;  
  
@Service  
public class TranTest2Service {  
    Logger log = LoggerFactory.getLogger(TranTest2Service.class);  
  
    //Send reminder SMS {1
        @PostConstruct //With this annotation, the method is executed once when the project starts
    @Async("taskExecutor")  
    public void sendMessage1() throws InterruptedException {  
        log.info("Sending SMS method---- 1   Execution start");  
        Thread.sleep(5000); //Simulation time
        log.info("Sending SMS method---- 1   end of execution");  
    }  
  
    //Send reminder SMS} 2
        @PostConstruct //With this annotation, the method is executed once when the project starts
    @Async("taskExecutor")  
    public void sendMessage2() throws InterruptedException {  
  
        log.info("Sending SMS method---- 2   Execution start");  
        Thread.sleep(2000); //Simulation time
        log.info("Sending SMS method---- 2   end of execution");  
    }  
}  

The @ Async("taskExecutor") in the code corresponds to the @ Bean("taskExecutor") in our custom thread pool, indicating that our custom thread pool is used.

The second class. Call the method of sending SMS (the asynchronous method cannot be in the same class as the called asynchronous method, otherwise it is invalid):

@Service  
public class OrderTaskServic {  
    @Autowired  
    private TranTest2Service tranTest2Service;  
  
    //Order processing task
    public void orderTask() throws InterruptedException {  
  
        this.cancelOrder(); //Cancel order
        tranTest2Service.sendMessage1(); //Methods of sending text messages 1
        tranTest2Service.sendMessage2(); //Method of sending SMS 2
  
    }  
  
    //Cancel order
    public void cancelOrder() throws InterruptedException {  
        System.out.println("Cancel order execution method------start");  
        System.out.println("Cancel order execution method------end ");  
    }  
  
}  

Operation screenshot:

Note that [nio-8090-exec-1] in the screenshot is the thread name of Tomcat

[Async-Service-1] and [Async-Service-2] represent thread 1 and thread 2. They are the thread names in our custom thread pool. The thread pool prefix we define in the configuration class:

private static final String threadNamePrefix = "Async-Service-"; // The thread pool name prefix indicates that our custom thread pool has been used.

matters needing attention

The following methods will invalidate @ Async

  • Asynchronous methods are decorated with static

  • The asynchronous class does not use @ Component annotation (or other annotations), so spring cannot scan the asynchronous class

  • An asynchronous method cannot be in the same class as the called asynchronous method

  • Class needs to be automatically injected with annotations such as @ Autowired or @ Resource. You cannot manually create a new object

  • If the SpringBoot framework is used, the @ EnableAsync annotation must be added to the startup class

Topics: Java Spring Spring Boot