Basic case, synchronous call and execution
1. Create a new springboot project
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sync</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.sync.demo.DemoApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2. Code structure
3. Imitate business code
@RestController public class OnBoradingController { @Autowired private OnBoardingService service; @RequestMapping("/b") public boolean onBoarding(){ System.out.printf("controller thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName()); return service.onBoard(); } }
@Service public class OnBoardingService { @Autowired private DownService service; public boolean onBoard(){ System.out.printf("Successful account opening\n"); System.out.printf("Enter the next stage\n"); service.downloadDoc(); System.out.printf("I finished it first\n"); return true; } }
@Service public class DownService { void downloadDoc() { System.out.printf("doc thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName()); System.out.printf("Start processing document%tT%n",new Date()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Processing document complete%tT%n",new Date()); } Future<String> Verification(){ System.out.printf("doc thread %s-%s handle\n",Thread.currentThread().getId(),Thread.currentThread().getName()); System.out.printf("Start validation request%tT%n",new Date()); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Verification passed%tT%n",new Date()); return new AsyncResult<>("636-ghd13-13sa"); } }
4. Startup class
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Modify to execute downloaddoc asynchronously (no return value)
5. Add asynchronous annotations to the startup class
@SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
6. Add asynchronous annotation to the method
@Async void downloadDoc() { ... }
- Asynchronous method cannot be defined as static type
- Calling method and asynchronous method cannot be defined in the same class
At this time, a total of two different thread IDs and names are output, which are asynchronous call execution
Next, perform verification asynchronously (with return value)
7. Add asynchronous annotation to the method
@Async Future<String> Verification(){ ... }
8. Call
public boolean onBoard(){ System.out.printf("Successful account opening\n"); Future<String> verification = service.Verification(); //Since the result is needed, I have to wait for the result, which is similar to that of synchronization while (!verification.isDone()){} try { System.out.printf("Verification successful id--%s\n",verification.get()); } catch (Exception e) { } System.out.printf("Enter the next stage\n"); service.downloadDoc(); System.out.printf("I finished it first\n"); return true; }
Thread pool
9. Add thread pool configuration
The default thread pool does not reuse threads and is a fake thread pool
@Configuration public class AscyConfig implements AsyncConfigurer { /** * Configure the thread pool to reduce the time required to create and destroy threads when calling each asynchronous method */ @Override public Executor getAsyncExecutor() { // Initialize the thread pool provided by the Spring framework ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // Number of core threads executor.setCorePoolSize(3); // Maximum number of threads executor.setMaxPoolSize(3); // Task waiting queue size executor.setQueueCapacity(10); // Task rejection policy: if the thread pool refuses to accept the task, the calling thread is used to execute executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // Define thread name prefix executor.setThreadNamePrefix("async-executor-"); // Call the thread pool initialization method. If the @ Bean annotation is added to getAsyncExecutor(), this method can not be called, because the ThreadPoolTaskExecutor implements the InitializingBean interface, and Spring will call InitializingBean when initializing the Bean afterPropertiesSet() executor.initialize(); return executor; } }
Multiple requests can be found that three threads in the thread pool process in turn
Name specifies the thread pool
10. Multiple thread pools
@Bean("asyncPool") @Override public Executor getAsyncExecutor() { ... } @Bean("asyncPool2") public Executor asyncPool2() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); return executor; } //The asynchronous thread pool with this name is called by default @Bean("taskExecutor") public Executor asyncPool3() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); return executor; }
//Specifies the thread pool name @Async("asyncPool2") Future<String> Verification(){...}
When there are multiple @ Async in the project and only some asynchronous thread pools are specified, the rule priority test for asynchronous methods without thread pools is as follows
- Override the Excutor returned by getAsyncExecutor of asyncconfigurator
- Find the default use name @ Bean("taskExecutor")
- Use the original default SimpleAsyncTaskExecutor(SimpleAsyncTaskExecutor is not a real thread pool. This class does not reuse threads, and a new thread will be created every time it is called)
Unified exception capture
- For asynchronous methods whose return value is Futrue:
a) One is to catch exceptions when calling get of future;
b) Catch exceptions directly in exception methods - For asynchronous methods whose return value is void: handle exceptions through AsyncUncaughtExceptionHandler
11. Add exception capture of return value void
@Configuration public class AscyConfig implements AsyncConfigurer { /** * void Return value asynchronous method exception capture and handling */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler(); } /** * Exception capture processing class */ public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { System.out.println(ex); } } }
Component development
Package into components for other project dependencies
12. Add an attribute class so that thread pool parameters can be read from yml
@ConfigurationProperties(prefix = "spring-async.async-thread-pool") public class AsyncThreadPoolProperties { /** * Whether to start the asynchronous thread pool. The default is false */ private boolean enable; /** * Number of core threads, default: number of available threads of Java virtual machine */ private Integer corePoolSize; /** * Maximum number of threads in thread pool, default: 40000 */ private Integer maxPoolSize; /** * Maximum number of threads in thread queue, default: 80000 */ private Integer queueCapacity; /** * Custom thread name prefix, default: async ThreadPool- */ private String threadNamePrefix; /** * Maximum idle time of thread in thread pool, default: 60, unit: seconds */ private Integer keepAliveSeconds = 60; /** * Whether the core thread is allowed to timeout. The default is false */ private boolean allowCoreThreadTimeOut; /** * IOC Whether to block and wait for the completion of the remaining tasks when the container is closed. Default: false (setAwaitTerminationSeconds must be set) */ private boolean waitForTasksToCompleteOnShutdown; /** * The closing time of blocking IOC container, default: 10 seconds (setwaitfortastocompleteonshutdown must be set) */ private int awaitTerminationSeconds = 10; ...get/set..... }
13. Corresponding yml
# apply name spring: application: name: asyncDemo # Application service WEB access port server: port: 8080 #Asynchronous thread pool #Asynchronous thread pool component switch. The default is false springAsync: asyncThreadPool: enable: true #Number of core threads, default: number of available threads of Java virtual machine core-pool-size: 4 #Maximum number of threads in thread pool, default: 40000 max-pool-size: 40000 #Maximum number of threads in thread queue, default: 80000 queue-capacity: 80000 #Custom thread name prefix, default: async ThreadPool- thread-name-prefix: Async-ThreadPool-acviti- #Maximum idle time of thread in thread pool, default: 60, unit: seconds keep-alive-seconds: 60 #Whether the core thread is allowed to timeout. The default is false allow-core-thread-time-out: false #Whether to block and wait for the execution of the remaining tasks when the IOC container is closed. Default: false (setAwaitTerminationSeconds must be set) wait-for-tasks-to-complete-on-shutdown : false #The closing time of blocking IOC container, default: 10 seconds (setwaitfortastocompleteonshutdown must be set) await-termination-seconds: 10
14. Modify the configuration class and read the property file and bean loading conditions
@Configuration @EnableConfigurationProperties(AsyncThreadPoolProperties.class) @ConditionalOnProperty(prefix = "spring-async.async-thread-pool", name = "enable", havingValue = "true", matchIfMissing = false) public class AscyConfig implements AsyncConfigurer { @Autowired private AsyncThreadPoolProperties asyncThreadPoolProperties; /** * Configure the thread pool to reduce the time required to create and destroy threads when calling each asynchronous method */ @Bean("asyncPool") @Override public Executor getAsyncExecutor() { //Number of processors available for the Java virtual machine int processors = Runtime.getRuntime().availableProcessors(); // Initialize the thread pool provided by the Spring framework ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // Number of core threads executor.setCorePoolSize(Objects.nonNull(asyncThreadPoolProperties.getCorePoolSize()) ? asyncThreadPoolProperties.getCorePoolSize() : processors); // Maximum number of threads executor.setMaxPoolSize(Objects.nonNull(asyncThreadPoolProperties.getCorePoolSize()) ? asyncThreadPoolProperties.getCorePoolSize() : processors); // Task waiting queue size executor.setQueueCapacity(10); // Task rejection policy: if the thread pool refuses to accept the task, the calling thread is used to execute executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // Define thread name prefix executor.setThreadNamePrefix(StringUtils.isEmpty(asyncThreadPoolProperties.getThreadNamePrefix()) ? "Async-ThreadPool-" : asyncThreadPoolProperties.getThreadNamePrefix()); // Call the thread pool initialization method. If the @ Bean annotation is added to getAsyncExecutor(), this method can not be called, because the ThreadPoolTaskExecutor implements the InitializingBean interface, and Spring will call InitializingBean when initializing the Bean afterPropertiesSet() // executor.initialize(); return executor; } ...... }
15. Create meta-inf / spring The purpose of the factories file is to tell springboot to load the bean, so that other projects will load the bean when it is dependent on other projects after packaging
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.sync.demo.config.AscyConfig
16. Move @ EnableAsync to the configuration class so that other items do not need to add this annotation
@EnableAsync @Configuration @EnableConfigurationProperties(AsyncThreadPoolProperties.class) @ConditionalOnProperty(prefix = "spring-async.async-thread-pool", name = "enable", havingValue = "true", matchIfMissing = false) public class AscyConfig implements AsyncConfigurer {...}
17. Modify the package plug-in classifier or skip**
- When the parameters are not modified, the jar packaged by springboot is a jar package that contains the executable dependent jar
- class will be copied to the new BOOT-INF/classes and displayed in manifest MF specifies the location
Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Archiver-Version: Plexus Archiver Built-By: Admin Start-Class: com.sync.demo.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Spring-Boot-Version: 2.3.7.RELEASE Created-By: Apache Maven 3.6.3 Build-Jdk: 1.8.0_40 Main-Class: org.springframework.boot.loader.JarLauncher
- If you rely on this jar package, the class will not be found
- The jar package that can be relied on is the renamed demo-0.0.1-snapshot jar. original
<!-- Packaged jar Is the original dependability jar,Suffix is no longer renamed to.jar.original--> <skip>true</skip>
<!-- Packaged jar Is the original dependability jar,Executable jar Renamed,Name stitching exec--> <classifier>exec</classifier>
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.sync.demo.DemoApplication</mainClass> <!-- <classifier><skip>Just keep one --> <!-- <classifier>exec</classifier> --> <skip>true</skip> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
18. After installing into maven library, start another springboot project
<!-- Add dependent coordinates when packaging --> <dependency> <groupId>com.sync</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
19. Configure enable: true to directly use @ Async asynchronous thread pool in the project
springAsync: asyncThreadPool: enable: true #Custom thread name prefix, default: async ThreadPool- thread-name-prefix: Async-ThreadPool-acviti-wohaha