Asynchronous call of @ Async annotation implementation method in Spring Boot project

1. Introduction

During the development of the company's projects, we often encounter the following scenarios:
1. When we call a third-party interface or method, we don't need to wait for the method to return to execute other logic. At this time, if the response time is too long, it will greatly affect the execution efficiency of the program. Therefore, we need to use asynchronous methods to execute our logic in parallel.
2. When performing time-consuming operations such as IO operations, we can also use asynchronous methods because they affect the customer experience and usage performance.
3. Similar applications, such as sending SMS, sending email or message notification, can use asynchronous methods appropriately.

be careful:
However, asynchronous operation increases the complexity of the code, so we should use it carefully. A little carelessness may produce unexpected results, thus affecting the whole logic of the program.

2. @ Async actual combat

First, add the @ EnableAsync annotation on the startup class.

2.1. Define AsyncController.java

@RestController
@Slf4j
public class AsyncController{

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/doAsync")
    public void doAsync() {
        asyncService.async01();
        asyncService.async02();
        try {
            log.info("start other task...");
            Thread.sleep(1000);
            log.info("other task end...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2. Define AsyncService.java

 /**
     * @author: dws
     * @date: 2021/9/22 15:07
     * @description: Test asynchronous method
  */
@Slf4j
@Service
public class AsyncService{

    @Async
    public void async01() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }

    @Async
    public void async02() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        System.out.println(1/0);
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }
}

Execution results:

It can be seen from the log that the methods async01 and async02 do not affect the execution of subsequent code segments, and even if the method throws an exception, it will not affect the operation of other code. At this time, we successfully called the asynchronous method.

3. @ Async failure problem resolution

For example:

/**
     * @author: dws
     * @date: 2021/9/22 15:07
     * @description: Test asynchronous method
  */
@Slf4j
@Service
public class AsyncService{

	//If you call an asynchronous method directly in an asynchronous class, @ Async will fail
	public void async0() {
		async01();
		async02();
	}

    @Async
    public void async01() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }

    @Async
    public void async02() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        System.out.println(1/0);
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }
}
@RestController
@Slf4j
public class AsyncController{

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/doAsync")
    public void doAsync() {
        asyncService.async0();
        try {
            log.info("start other task...");
            Thread.sleep(1000);
            log.info("other task end...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

If you call an asynchronous method directly in an asynchronous class, @ Async will fail.

3.1 cause analysis

The reason for the failure is that we directly call the async01() and async02() methods in the test() method, which is equivalent to this.async01() and this.async02(), that is, the AsyncService object itself calls the async01() and async02() methods, and the * * @ async and @ Transactional * * annotations essentially use dynamic agents, It should be the proxy object of AsyncService that calls the async01() and async02() methods. In fact, during the initialization of the Spring container, the Spring container will "replace" the class object containing AOP annotation with a proxy object (simply understood in this way), so the reason for the invalidation of the annotation is obvious, that is, because the calling method is the object itself rather than the proxy object, and because it has not passed through the Spring container, the solution will follow this idea.

Many blogs on the Internet say that the solution is to extract the methods to be executed asynchronously into a class, which can indeed solve the problem of asynchronous annotation failure. The principle is that when you extract the methods to be executed asynchronously into a class, this class must be managed by Spring, and other Spring components will be injected when they need to be called, At this time, the proxy class is actually injected. In fact, there are other solutions. It is not necessary to extract it into a class separately.

3.1 solution I

Calling an asynchronous method cannot be in the same class as an asynchronous method. For example: "2. Actual combat".

3.2 solution II

Get your own proxy object through the context in AsyncService and call asynchronous methods.

/**
     * @author: dws
     * @date: 2021/9/22 15:07
     * @description: Test asynchronous method
  */
@Slf4j
@Service
public class AsyncService{
	
	@Autowired
    ApplicationContext context;

	//If you call an asynchronous method directly in an asynchronous class, @ Async will fail
	public void async0() {
	 	AsyncService asyncService = context.getBean(AsyncService.class);
		asyncService.async01();
		asyncService.async02();
	}

    @Async
    public void async01() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }

    @Async
    public void async02() {
        log.info("thread{} start at{}", Thread.currentThread().getName(), System.currentTimeMillis());
        System.out.println(1/0);
        log.info("thread{} end at{}", Thread.currentThread().getName(), System.currentTimeMillis());
    }
}

4. Summary

Note:
1. The @ EnableAsync annotation must be added to the startup class;
2. Asynchronous classes are not managed by springboot, add @ Component annotation (or other annotations) 3, and ensure that asynchronous classes can be scanned;
4. The test asynchronous method cannot be in the same class as the asynchronous method;
5. For asynchronous classes that need to be initialized with spring container in the test class, you cannot manually create new objects yourself;

If an asynchronous method has a return value, it can be written as follows:

	@Async
    Future<ByteFuture> getInputStreamFuture(String fileName) {
        ByteFuture byteFuture = new ByteFuture();
        byteFuture.setFileName(fileName);
        try (InputStream inputStream = new URL(fileName.replace(" ", "%20") + "?" + Math.random()).openStream()) {
            log.info("Download File:{}", fileName);
            byteFuture.setBytes(IOUtils.toByteArray(inputStream));
        } catch (Exception e) {
            log.error("Failed to download file:{}", fileName, e);
        }
        return new AsyncResult<>(byteFuture);
    }

How to get asynchronous return value:

Future<ByteFuture> future = asyncService.getInputStreamFuture("");
ByteFuture byteFuture = future.get();

Keywords: Java Spring Spring Boot

Added by predator12341 on Mon, 18 Oct 2021 09:00:41 +0300