Spring Aysnc Best Practice (1): Principles and Limitations

According to my observation, both beginners and advanced developers are popular to use Spring Boot to build their own programs. Spring Boot's "convention is better than configuration" style allows everyone to focus on business logic during development. When you need it, you can easily understand the working mechanism of Spring by consulting the Spring Boot tutorial. Although most of the time it can be done by just adding a few annotations, sometimes it is necessary to understand the operating mechanism behind it so that you can use Spring Boot more professionally.


This article will try to introduce how to perform asynchronous processing in Spring.


Asynchronous processing applies to parts that are not directly related to business logic (cross-cutting concerns) or are not input to other business logic, and can also be decoupled in distributed systems.


*Annotation: Cross-cutting concerns refer to special concerns that have behaviors that cross multiple modules and cannot achieve effective modularity using traditional software development methods. *


In Spring, the `@Async` annotation can mark asynchronous operations. However, there are some restrictions when using `@Async`, just adding it to a method does not ensure that the method will be executed in a separate thread. If you only use `@Async` occasionally, you need to be extra careful.


1. @Async working mechanism


First add the `Async` annotation to the method. Next, Spring will create a proxy (JDK Proxy/CGlib) for the objects defined by `Async` based on the `proxyTargetClass` property. Finally, Spring will try to search the thread pool related to the current context and submit the method as an independent execution path. To be precise, Spring will search for the only `TaskExecutor` bean or the bean named `taskExecutor`. If it is not found, the default `SimpleAsyncTaskExecutor` is used.


To complete the above process, you need to pay attention to several restrictions in use, otherwise `Async` will not work.


2. @Async limitations


1. You must use `@Async` in classes marked with `@ComponentScan` or `@configuration`.


2.1 Use Async annotations in classes


```java
package com.example.ask2shamik.springAsync.demo;

import java.util.Map;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class AsyncMailTrigger {
   @Async
   public void senMail(Map<String,String> properties)
{
       System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
       properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
   }
}
```


2.2 Caller class


```java
package com.example.ask2shamik.springAsync.demo;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AsyncCaller {
   @Autowired
   AsyncMailTrigger asyncMailTriggerObject;

   public void rightWayToCall() {
       System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
       asyncMailTriggerObject.senMail(populateMap());
   }
   
   public void wrongWayToCall() {
       System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
       AsyncMailTrigger asyncMailTriggerObject = new AsyncMailTrigger();
       asyncMailTriggerObject.senMail(populateMap());
   }

   private Map<String,String> populateMap(){
       Map<String,String> mailMap= new HashMap<String,String>();
       mailMap.put("body", "A Ask2Shamik Article");
       return mailMap;
   }
}
```


In the above example, the `AsyncMailTrigger` using `@Autowired` is managed by `@ComponentScan`, so a new thread is created for execution. The local object created in the `WrongWayToCall` method is not managed by `@ComponentScan` and no new thread is created.


 2.3 Output


```shell
Calling From rightWayToCall Thread main
2019-03-09 14:08:28.893  INFO 8468 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
Trigger mail in a New Thread :: task-1
Key::body Value ::A Ask2Shamik Article
++++++++++++++++
Calling From wrongWayToCall Thread main
Trigger mail in a New Thread :: main
Key::body Value ::A Ask2Shamik Article
```


2. Do not use the `@Async` annotation on the `private` method. Since the agent cannot be created at runtime, it does not work.


```java
@Async
private void senMail()
{
   System.out.println("A proxy on Private method "  + Thread.currentThread().getName());
}
```



3. The `caller` method and the `@Async` method of calling `methodAsync` should be defined in different classes. Otherwise, although the proxy object is created, `caller` will bypass the proxy and call the method directly without creating a new thread.


(https://dzone.com/storage/temp/11422746-springasync.jpg)


2.4 Example


```java
package com.example.ask2shamik.springAsync.demo;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class AsyncCaller {
   @Autowired
   AsyncMailTrigger asyncMailTriggerObject;

   public void rightWayToCall() {
       System.out.println("Calling From rightWayToCall Thread " + Thread.currentThread().getName());
       asyncMailTriggerObject.senMail(populateMap());
   }

   public void wrongWayToCall() {
       System.out.println("Calling From wrongWayToCall Thread " + Thread.currentThread().getName());
       this.senMail(populateMap());
   }

   private Map<String,String> populateMap(){
       Map<String,String> mailMap= new HashMap<String,String>();
       mailMap.put("body", "A Ask2Shamik Article");
       return mailMap;
   }

   @Async
   public void senMail(Map<String,String> properties)
{
       System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
       properties.forEach((K,V)->System.out.println("Key::" + K + " Value ::" + V));
   }
}
```


Finally, the `@EnableAsync` annotation should be used when executing. Its role is to make Spring submit the `@Async` method in the background thread pool. To customize the `Executor`, implement the bean yourself. Specific examples will be given in the next article.


```java
package com.example.ask2shamik.springAsync;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import com.example.ask2shamik.springAsync.demo.AsyncCaller;

@SpringBootApplication
@EnableAsync
public class DemoApplication {
   @Autowired
   AsyncCaller caller;

   public static void main(String[] args) {
       SpringApplication.run(DemoApplication.class, args);
   }

   @Bean
   public CommandLineRunner commandLineRunner(ApplicationContext ctx)
{
       return args -> {
       caller.rightWayToCall();
       Thread.sleep(1000);
       System.out.println("++++++++++++++++");
       Thread.sleep(1000);
       caller.wrongWayToCall();
       };
   }
}
```


 3. Summary


Hope that the explanation in this article can help understand Async's internal mechanism and restrictions in use. The next article will discuss exception handling in Async. Stay tuned!



Guess you like

Origin blog.51cto.com/15082395/2590376