In this post we will be discussing about spring boot asynchronous execution support using async task executor feature to execute task in a different thread. We will take a look into configuring SimpleAsyncTaskExecutor, ConcurrentTaskExecutor, ThreadPoolExecutor in a spring project. Apart from this, we will be also looking into how actual method return type can be wrapped in a Future object while dealing with async behaviour in spring.So let us get started with spring boot async task executor.
Async Configuration in Spring
To enable async behaviour in Spring, annotate your configuration class with @EnableAsync.
@EnableAsync @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
@EnableAsync: - It detects @Async annotation.
mode - The mode() attribute controls how advice is applied. By default its value is AdviceMode.PROXY. Note that if the mode() is set to AdviceMode.ASPECTJ, then the value of the proxyTargetClass() attribute will be ignored. Note also that in this case the spring-aspects module JAR must be present on the classpath.
proxyTargetClass - It defines the type of proxy that would be used from CGLIB or JDK.By default its CGLIB.
Using @Async Annotation
This annotation is used on the method level for those method which you want it execution to be in a seperate thread.This annotation works as expected if a public method is annotated with this annotation.
Also, the method needs to be called from a different class so that it can be proxied else the proxy will be bypassed.
Following is an example of an @Async annotated method. It does not return any value.
@Override @Async public void createUserWithDefaultExecutor(){ //SimpleAsyncTaskExecutor System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with default executor"); }
By default, Spring will be searching for an associated thread pool definition: either a unique TaskExecutor bean in the context, or an Executor bean named "taskExecutor" otherwise. If neither of the two is resolvable, a SimpleAsyncTaskExecutor will be used to process async method invocations
Using @Async Annotation with Method Return Type
The actual return type of a method can be wrapped in a Future object.
@Override @Async public FuturecreateAndReturnUser() { System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); try { User user = new User(); user.setFirstName("John"); user.setLastName("Doe"); user.setGender("Male"); Thread.sleep(5000); return new AsyncResult (user); } catch (InterruptedException e) { System.out.println(e.getMessage()); } return null; }
Following is a test method for this.
@Test public void createAndReturnUserTest() throws ExecutionException, InterruptedException { System.out.println("Current Thread in test class " + Thread.currentThread().getName()); long startTime = System.currentTimeMillis(); FuturefutureUser = userService.createAndReturnUser(); futureUser.get(); assertTrue((System.currentTimeMillis() - startTime) >= 5000); }
Other Interesting Posts Spring Boot Hibernate 5 Example Spring Hibernate Integration Example Spring Boot Actuator Rest Endpoints Example Spring 5 Features and Enhancements Spring Boot Thymeleaf Example Securing REST API with Spring Boot Security Basic Authentication Spring Boot Security Password Encoding using Bcrypt Encoder Spring Security with Spring MVC Example Using Spring Boot Websocket spring Boot Integration Without STOMP with complete JavaConfig
Defining ThreadPoolTaskExecutor and ConcurrentTaskExecutor at Method Level
By default spring uses SimpleAsyncTaskExecutor to run methods annotated with @Async. We can also define our custom executor bean as follow and use it at method level.
ThreadPoolTaskExecutor@Bean(name = "threadPoolExecutor") public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("threadPoolExecutor-"); executor.initialize(); return executor; }ConcurrentTaskExecutor
@Bean(name = "ConcurrentTaskExecutor") public TaskExecutor taskExecutor2 () { return new ConcurrentTaskExecutor( Executors.newFixedThreadPool(3)); }
These beans can be used at method level in following ways
@Override @Async("threadPoolExecutor") public void createUserWithThreadPoolExecutor(){ System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with thread pool executor"); } @Override @Async("ConcurrentTaskExecutor") public void createUserWithConcurrentExecutor(){ System.out.println("Currently Executing thread name - " + Thread.currentThread().getName()); System.out.println("User created with concurrent task executor"); }
SimpleAsyncTaskExecutor does make sense in cases, if you want to execute some long-time-executing tasks, e.g. if you want to compress log files at the end of a day. In other cases, if you want to execute a short-time-executing task every n seconds or minutes, you should use the ThreadPoolTaskExecutor, because of reusing of system resources.
Implementing Executor at Application Level
To implement executor at application level, we require to implement AsyncConfigurer and override folowing methods.
@Configuration public class AsyncConfig 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; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncExceptionHandler(); } }
Following is the exception handler.
AsyncExceptionHandler.classpublic class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... obj) { System.out.println("Exception Cause - " + throwable.getMessage()); System.out.println("Method name - " + method.getName()); for (Object param : obj) { System.out.println("Parameter value - " + param); } } }
Conclusion
I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.