SpringBoot asynchronous requests and calls

From: Micro reading https://www.weidianyuedu.com/content/0117397506434.html

1, Use of asynchronous requests in Spring Boot**

1. Asynchronous request and synchronous request

Synchronization request

Asynchronous request

Features: you can first release the threads and related resources allocated by the container to the request, reduce the system burden, and release the request of the thread allocated by the container. Its response will be delayed. You can respond to the client when the time-consuming processing is completed (such as long-time operation). Bottom line: it increases the throughput of the server to client requests (we use less in actual production. If there are a large number of concurrent requests, we will load the requests to each node of the cluster service through nginx to share the request pressure. Of course, we can also buffer the requests through the message queue).

2. Implementation of asynchronous request

Method 1: realize asynchronous request in Servlet mode

@RequestMapping(value = "/email/servletReq", method = GET)  public void servletReq (HttpServletRequest request, HttpServletResponse response) {      AsyncContext asyncContext = request.startAsync();      //Set listener: you can set the callback processing of its start, completion, Exception, timeout and other events AddListener (new "AsyncListener() {@ Override" public "void" onTimeout(AsyncEvent "event) throws" IOException {System.out.println("timeout...); / / do some operations after timeout } @ Override public void onStartAsync(AsyncEvent event) throws {IOException {System.out.println("thread start"); } @ Override public void onError(AsyncEvent event) throws {IOException {System.out.println("error occurred:" + event.getThrowable()); } @ Override public void onComplete(AsyncEvent event) throws {IOException {System.out.println("execution completed"); / / here you can do some operations to clean up resources           }      }); / / set the timeout time} asynccontext setTimeout(20000);       asyncContext.start(new) Runnable() {@ Override public void run() {try {Thread.sleep(10000); System.out.println("internal thread:" + Thread.currentThread().getName()) );                   asyncContext.getResponse().setCharacterEncoding("utf-8");                   asyncContext.getResponse().setContentType("text/html;charset=UTF-8");                   asyncContext.getResponse().getWriter().println("this is an asynchronous request return"); } catch (Exception E) {System.out.println("Exception:" + e); } / / asynchronous request completion notification / / the whole request is completed at this time complete();           }      }); / / at this time, the thread connection such as {request has released} system out. Println ("main thread:" + thread. Currentthread() getName());   }

Method 2: it is very simple to use. The directly returned parameters can be wrapped with a layer of label. You can inherit the WebMvcConfigurerAdapter class to set the default thread pool and timeout processing

@RequestMapping(value = "/email/callableReq", method = GET)  @ResponseBody  public Callable<String> callableReq () {      System.out.println("External thread:" + Thread.currentThread().getName());      return new Callable<String>() {          @Override          public String call() throws Exception {              Thread.sleep(10000);              System.out.println("Internal thread:" + Thread.currentThread().getName());              return "callable!";          }      };  }  @Configuration  public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {  @Resource  private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;  @Override  public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {      //Processing} callable timeout} configurer setDefaultTimeout(60*1000);       configurer.setTaskExecutor(myThreadPoolTaskExecutor);       configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());   }  @Bean  public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {      return new TimeoutCallableProcessingInterceptor();   }}

Method 3: similar to method 2, set a timeout callback for WebAsyncTask in the Callable outsourcing layer to realize timeout processing

@RequestMapping(value = "/email/webAsyncReq", method = GET)    @ResponseBody    public WebAsyncTask<String> webAsyncReq () {        System.out.println("External thread:" + Thread.currentThread().getName());        Callable<String> result = () -> {            System.out.println("Internal thread start:" + Thread.currentThread().getName());            try {                TimeUnit.SECONDS.sleep(4);            } catch (Exception e) {                // TODO: handle exception            }            logger.info("secondary thread return");             System.out.println("internal thread return:" + thread. Currentthread() getName());             return "success";         };         WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);         wat.onTimeout(new callable < string > () {@ Override) public string call() throws exception {/ / todo auto generated method stub return "timeout";             }        });         return wat;     }

Mode 4: DeferredResult can handle some relatively complex business logic. The most important thing is that it can process and return business in another thread, that is, it can communicate between two completely unrelated threads.

@RequestMapping(value = "/email/deferredResultReq", method = GET)    @ResponseBody    public DeferredResult<String> deferredResultReq () {        System.out.println("External thread:" + Thread.currentThread().getName());        //Set the timeout time: deferredresult < string > result = new deferredresult < string > (60 * 1000L); / / handle timeout events using the delegation mechanism} result Ontimeout (new. Runnable() {@ Override. Public. Void. Run() {System.out.println("DeferredResult timeout"); result.setResult("timeout!");             }        });         result.onCompletion(new) runnable() {@ Override public void run() {/ / after completion, @ System.out.println("call completed");             }        });         myThreadPoolTaskExecutor.execute(new) runnable() {@ Override public void run() {/ / process business logic System.out.println("internal thread:" + Thread.currentThread().getName()) ); / / return result setResult("DeferredResult!!");             }        });        return result;     }

2, Use of asynchronous calls in Spring Boot

1. Introduction

Processing of asynchronous requests. In addition to asynchronous requests, we generally use asynchronous calls. Usually, in the development process, we will encounter a method that is irrelevant to the actual business and has no tightness. For example, record log information and other businesses. At this time, it is normal to start a new thread to do some business processing, and let the main thread execute other businesses asynchronously.

2. Usage (based on spring)

You need to add @ EnableAsync to the startup class to make the asynchronous call @ Async annotation effective. You can add this annotation to the methods that need asynchronous execution, @ Async ("ThreadPool"), which is a custom thread pool. Code slightly... Just two labels. Just try one yourself

3. Precautions

By default, when TaskExecutor is not set, the thread pool SimpleAsyncTaskExecutor is used by default, but this thread is not a real thread pool because threads are not reused and a new thread will be created every time. It can be seen from the console log output that the thread name is incremented each time. So it's best to customize a thread pool. The asynchronous method called cannot be the method of the same class (including the internal class of the same class). In short, Spring will create a proxy class for it when starting the scan, and the same kind of call still calls its own proxy class, so it is the same as normal calls. The same is true for other annotations such as @ Cache. To put it bluntly, it is caused by Spring's proxy mechanism. Therefore, in development, it is best to take asynchronous services out of a separate class for management. The following will focus on..

4. Under what circumstances will @ Async asynchronous methods fail?

Call the same class and bet @ Async asynchronous method:

In spring, annotations such as @ Async, @ Transactional and cache essentially use dynamic proxies. In fact, when the spring container initializes, the spring container will "replace" the class object containing AOP annotation with a proxy object (simply understood), so the reason why the annotation fails is obvious, because the method is called by the object itself rather than the proxy object, Because there is no spring container, the solution will follow this idea.

The static method is called

Call the private method

5. How to solve problem 1 in 4 (just pay attention to the other two problems in 2 and 3)

Extract the methods to be executed asynchronously into a class. 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, what is actually injected is the proxy class. In fact, all our injected objects assign member variables to the current Spring component from the Spring container. Because some classes use AOP annotations, what actually exists in the Spring container is its proxy object. Then we can get our own proxy object through the context and call asynchronous methods.

@Controller@RequestMapping("/app")public class EmailController {    //There are many ways to obtain applicationContext objects, the simplest of which is @ Autowired , private , applicationContext , applicationContext;     @RequestMapping(value = "/email/asyncCall", method = GET)    @ResponseBody    public Map<String, Object > asynccall() {map < string, Object > resmap = new HashMap < string, Object > (); try {/ / calling asynchronous methods of the same kind does not work / / this.testAsyncTask() ; / / get your own proxy object through the context and call the asynchronous method {emailcontroller} emailcontroller = (emailcontroller) applicationContext getBean(EmailController.class);             emailController.testAsyncTask();             resMap.put("code",200);         }catch (Exception e) {            resMap.put("code",400);            logger.error("error!",e);         }        return resMap; } / / note that it must be public and non static method @ Async public void testasynctask() throws {InterruptedException {Thread.sleep(10000); System.out.println("asynchronous task execution completed!");     }}

Open the cglib proxy and manually obtain the Spring proxy class to call asynchronous methods under the same category. First, add the @ EnableAspectJAutoProxy (exposeProxy = true) annotation on the startup class. The code implementation is as follows:

@Service@Transactional(value = "transactionManager", readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)public class EmailService {    @Autowired    private ApplicationContext applicationContext;    @Async    public void testSyncTask() throws InterruptedException {        Thread.sleep(10000);        System.out.println("Asynchronous task execution completed!");    }    public void asyncCallTwo() throws InterruptedException {        //this.testSyncTask();//  EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);//  emailService.testSyncTask();         boolean isAop = AopUtils.isAopProxy(EmailController.class);// Whether it is a proxy object;         boolean isCglib = AopUtils.isCglibProxy(EmailController.class); / / whether it is a CGLIB proxy object;         boolean isJdk = AopUtils.isJdkDynamicProxy(EmailController.class); / / whether it is a proxy object of JDK dynamic proxy mode; / / the following are the key points!!!         EmailService emailService = (EmailService)applicationContext.getBean(EmailService.class);         EmailService proxy = (EmailService) AopContext.currentProxy();         System.out.println(emailService == proxy ? true : false);         proxy.testSyncTask();         System.out.println("end!!!");     }}

3, The difference between asynchronous request and asynchronous call is different. Asynchronous request is used to solve the pressure on the server caused by concurrent requests, so as to improve the throughput of requests; Asynchronous call is used to do some tasks that are not mainline processes and do not require real-time calculation and response, such as synchronizing logs to kafka for log analysis. The asynchronous request will always wait for the corresponding response, and the result needs to be returned to the client; For asynchronous calls, we often return the response to the client immediately to complete the whole request. As for the task of asynchronous calls, the background can run slowly, and the client won't care.

4, To summarize the use of asynchronous requests and asynchronous calls, it's almost here. I hope you can point out more problems. This article mentioned dynamic agent, and the implementation principle of Aop in spring is dynamic agent. In the future, we will make a detailed interpretation of dynamic agent, and we hope to have more support.

Keywords: Java Programming Spring Boot Back-end

Added by borris_uk on Thu, 20 Jan 2022 16:50:21 +0200