Usage and principle of Spring @Async annotation

Posted by acroporas on Sun, 26 Dec 2021 07:31:36 +0100

The method marked with @ Async annotation in Spring is called asynchronous method. It will be executed in an independent thread other than the current thread of the caller. In fact, it is equivalent to our own new thread (() - > system out. println("hello world !")) In this way, the corresponding business logic is executed in another thread. This article only talks about the use of @ Async, and then analyzes its implementation principle.

@Async annotation usage conditions:

1.@Async annotation is generally used on the methods of a class. If it is used on a class, all the methods of the class are executed asynchronously;
2. The class object of the @ Async annotation method used should be a bean object managed by the Spring container;
3. The annotation @ EnableAsync needs to be configured on the calling asynchronous method class
 

Usage Note:

1, By default (that is, @ EnableAsync annotated mode=AdviceMode.PROXY). If a method modified by @ Async annotation is not called inside the same class, it will not be executed asynchronously. This is similar to the @ Transitional annotation, and the underlying layer is implemented through dynamic proxy. If you want to realize the asynchrony of self calling inside the class, you need to switch the @ EnableAsync annotated mode = ad viceMode. AspectJ, see @ EnableAsync annotation for details.

2. Any parameter type is supported, but the return value of the method must be of type void or Future. When using Future, you can use the ListenableFuture interface that implements the Future interface or the completable Future class to better interact with asynchronous tasks. If the asynchronous method has a return value and does not use the Future < V > type, the caller cannot obtain the return value.
 

 

@Async source code

We use spring-context-5.0 5.RELEASE. Jar package. Let's take a look at the source code of @ Async #:

/**
 * Annotation that marks a method as a candidate for <i>asynchronous</i> execution.
 * Can also be used at the type level, in which case all of the type's methods are
 * considered as asynchronous.
 *(This annotation marks a method executed asynchronously. If it is used on a class, all methods of the class are executed asynchronously.)
 *
 * <p>In terms of target method signatures, any parameter types are supported.
 * However, the return type is constrained to either {@code void} or
 * {@link java.util.concurrent.Future}. In the latter case, you may declare the
 * more specific {@link org.springframework.util.concurrent.ListenableFuture} or
 * {@link java.util.concurrent.CompletableFuture} types which allow for richer
 * interaction with the asynchronous task and for immediate composition with
 * further processing steps.
 * (Any parameter type is supported, but the return value of the method must be of type void or Future. When using Future, you can use 
 * The ListenableFuture interface that implements the Future interface or the completable Future class can better interact with asynchronous tasks.)
 *
 * <p>A {@code Future} handle returned from the proxy will be an actual asynchronous
 * {@code Future} that can be used to track the result of the asynchronous method
 * execution. However, since the target method needs to implement the same signature,
 * it will have to return a temporary {@code Future} handle that just passes a value
 * through: e.g. Spring's {@link AsyncResult}, EJB 3.1's {@link javax.ejb.AsyncResult},
 * or {@link java.util.concurrent.CompletableFuture#completedFuture(Object)}.
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 3.0
 * @see AnnotationAsyncExecutionInterceptor
 * @see AsyncAnnotationAdvisor
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
 
	/**
	 * A qualifier value for the specified asynchronous operation(s).
	 * <p>May be used to determine the target executor to be used when executing this
	 * method, matching the qualifier value (or the bean name) of a specific
	 * {@link java.util.concurrent.Executor Executor} or
	 * {@link org.springframework.core.task.TaskExecutor TaskExecutor}
	 * bean definition.
     * (You can match custom task executors -- > executor types or bean s of task executor types)
	 * <p>When specified on a class level {@code @Async} annotation, indicates that the
	 * given executor should be used for all methods within the class. Method level use
	 * of {@code Async#value} always overrides any value set at the class level.
     * (The setting on the method overrides the setting on the class)
	 * @since 3.1.2
	 */
	String value() default "";
 
}

 

After reading the source code of @ Async and the Executor, TaskExecutor and other classes involved, we can roughly infer that the underlying @ Async uses thread pool technology, which is exactly the case.  

Test:

Let's do a test in the Spring Boot project. The test code is as follows:

The asynchronous method is defined and implemented as follows:

 

package com.example.service;
 
import org.springframework.scheduling.annotation.Async;
 
 
public interface TestService {
    @Async
    void test();
}

 

package com.example.service.impl;
 
import com.example.service.TestService;
import org.springframework.stereotype.Service;
 
@Service
public class TestServiceImpl implements TestService {
    @Override
    public void test() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test Spring Asynchronous call!");
    }
}

 

The controller class code for calling asynchronous methods is as follows:

package com.example.demo;
 
import com.example.model.PramInfo;
import com.example.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.*;
 
 
@RestController
@RequestMapping(value = "/test")
@EnableAsync
public class TestContoller {
 
    @Autowired
    private TestService testService;
 
    @GetMapping(value = "/testAsync")
    public void print() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        System.out.println("The current thread starts executing the test function......");
        testService.test();
        for (int i = 1; i <= 100; i++) {
            System.out.print(i + " ");
            if (i % 10 == 0) {
                System.out.println();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("The current thread test function has finished executing......");
    }
}

 

After the program starts, the browser accesses http://localhost:8080/test/testAsync , the spooling log is as follows:

ThreadName:http-nio-8080-exec-5
 The current thread starts executing the test function......
1 2 3 4 5 6 7 8 9 10 
ThreadName:SimpleAsyncTaskExecutor-2
 test Spring Asynchronous call!
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 
61 62 63 64 65 66 67 68 69 70 
71 72 73 74 75 76 77 78 79 80 
81 82 83 84 85 86 87 88 89 90 
91 92 93 94 95 96 97 98 99 100 
The current thread test function has finished executing......

 

It can be seen that there are two threads executing.

The debugger can see that the testService has been replaced with the bean generated by the dynamic proxy:

 

 

If we remove the @ EnableAsync or new TestService object on the TestController (the object is not loaded into the Spring container), the print() method in the TestController will be executed synchronously. You can also see that only one thread is executing in the background print log:

 

ThreadName:http-nio-8080-exec-5
 The current thread starts executing the test function......
ThreadName:http-nio-8080-exec-5
 test Spring Asynchronous call!
1 2 3 4 5 6 7 8 9 10 
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 
61 62 63 64 65 66 67 68 69 70 
71 72 73 74 75 76 77 78 79 80 
81 82 83 84 85 86 87 88 89 90 
91 92 93 94 95 96 97 98 99 100 
The current thread test function has finished executing......

 

If we make some simple modifications to TestService, @ Async modifies the test() method, and test2() method does not modify:

public interface TestService {
    @Async
    void test();
 
    void test2();
}
 
public class TestServiceImpl implements TestService{
    @Override
    public void test() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test Spring Asynchronous call!");
    }
 
    @Override
    public void test2() {
        test();//Self calling test() method
    }
}

controller class:

@GetMapping(value = "/testAsync")
    public void print() {
        System.out.println("ThreadName:" + Thread.currentThread().getName());
        System.out.println("The current thread starts executing the test function......");
        testService.test2();//Instead, call the test2() method without @ Async annotation
        for (int i = 1; i <= 100; i++) {
            System.out.print(i + " ");
            if (i % 10 == 0) {
                System.out.println();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("The current thread test function has finished executing......");
    }

 

At this time, you can see the test results. The test() method called by test2() method does not execute asynchronously, but only has one thread, as follows:

ThreadName:http-nio-8080-exec-1
 The current thread starts executing the test function......
ThreadName:http-nio-8080-exec-1
 test Spring Asynchronous call!
1 2 3 4 5 6 7 8 9 10 
11 12 13 14 15 16 17 18 19 20 
21 22 23 24 25 26 27 28 29 30 
31 32 33 34 35 36 37 38 39 40 
41 42 43 44 45 46 47 48 49 50 
51 52 53 54 55 56 57 58 59 60 

Later, we will summarize and analyze the implementation principle of @ Async annotation, configure our own defined executors, and analyze exception handling and transaction processing in asynchronous situations.