Spring Boot asynchronous request and asynchronous call

I collated Java advanced materials for free, including Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo high concurrency distributed and other tutorials, a total of 30G, which needs to be collected by myself.
Portal: https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ

  • 1, The use of asynchronous requests in Spring Boot

1. Asynchronous request and synchronous request

 

 

 

 

 

Characteristic:

The thread and related resources allocated by the container to the request can be released first to reduce the burden of the system, and the request of the thread allocated by the container can be released. The response will be delayed, and the client can respond when the time-consuming processing is completed (such as a long-time operation).

Bottom line: it increases the throughput of requests from the server to the client (we use less in actual production. If the concurrent requests are large, we will load the requests to each node of the cluster service through nginx to share the request pressure, and of course, we can buffer the requests through the message queue).

 

2. Implementation of asynchronous request

 

Mode 1: implement 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 events such as start, finish, exception, timeout, etc
      asyncContext.addListener(new AsyncListener() {
          @Override
          public void onTimeout(AsyncEvent event) throws IOException {
              System.out.println("Overtime...");
              //Do some related 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("An 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 timeout
      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 entire request is now complete
              asyncContext.complete();
          }
      });
      //At this point, the thread connection of request has been released
      System.out.println("Main thread:" + Thread.currentThread().getName());
  }

Method 2: it is very simple to use. The parameters returned directly wrap a layer of callable. 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();
  }
}

 

Mode 3: similar to mode 2. In the Callable outsourcing layer 1, set a timeout callback for the WebAsyncTask to implement 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("Sub thread return");
            System.out.println("Internal thread returned:" + 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 "overtime";
            }
        });
        return wat;
    }

 

Mode 4: DeferredResult can handle some relatively complex business logic. The most important thing is to process and return business in another thread, which 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 timeout
        DeferredResult<String> result = new DeferredResult<String>(60*1000L);
        //Handling timeout events with delegation mechanism
        result.onTimeout(new Runnable() {

            @Override
            public void run() {
                System.out.println("DeferredResult overtime");
                result.setResult("Overtime!");
            }
        });
        result.onCompletion(new Runnable() {

            @Override
            public void run() {
                //After completion
                System.out.println("Call completion");
            }
        });
        myThreadPoolTaskExecutor.execute(new Runnable() {

            @Override
            public void run() {
                //Process business logic
                System.out.println("Internal thread:" + Thread.currentThread().getName());
                //Return result
                result.setResult("DeferredResult!!");
            }
        });
       return result;
    }

 

2, The use of asynchronous call in Spring Boot

 

1, introduction

 

Processing of asynchronous requests. In addition to asynchronous requests, we usually use asynchronous calls. Usually in the development process, there is a method that has nothing to do with the actual business and has no compactness. Such as logging 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.

Episode: more Spring Boot related articles can be restored to official account (Java back end) by technical blog.

2. Usage (based on spring)

 

You need to add @ EnableAsync to the startup class to make the asynchronous call @ Async annotation take effect

Add this annotation to the method requiring asynchronous execution to @ async ("ThreadPool"), which is a custom thread pool.

Code omitted... Just two labels. Just try one on your own

3. Precautions

 

By default, when TaskExecutor is not set, SimpleAsyncTaskExecutor is used as the thread pool by default, but this thread is not a real thread pool, because the thread is not reused, and a new thread will be created every time it is called. You can see from the console log output that the output thread name is incremented every time. So it's best to come from defining a thread pool.

The asynchronous method to be called cannot be the method of the same class (including the internal class of the same class). Simply speaking, because Spring will create a proxy class for it when it starts scanning, and when it calls the same kind, it still calls its own proxy class, so it is the same as the normal call.

Other annotations, such as @ Cache, are the same. To put it bluntly, they are caused by Spring's proxy mechanism. Therefore, in development, it is better to separate asynchronous services out of a class to manage. The following will focus on..

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

 

Calling the same class bets @ Async asynchronous method:

 

In spring, annotations such as @ Async and @ Transactional and cache use dynamic agents in essence. In fact, when spring container initializes, it will "replace" the class object with AOP annotation as the proxy object (in a simple way), so the reason for annotation failure is obvious, because it is the object itself rather than the proxy object that calls the method, because there is no proxy object After the spring container, the solution will follow this idea.

 

static methods are called

Call (private) privatization method

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

 

Extract the method to be executed asynchronously into a class. The principle is that when you extract the method to be executed asynchronously into a class, the class must be managed by Spring, and other Spring components must be injected when they need to be called. At this time, the agent class is actually injected.

 

In fact, our injection objects all assign member variables to the current Spring components 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 to call asynchronous methods through context.

@Controller
@RequestMapping("/app")
public class EmailController {

    //There are many ways to get ApplicationContext objects. This is the simplest way. Let's learn about other ways
    @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{
            //In this way, it does not work to call asynchronous methods under the same category
            //this.testAsyncTask();
            //Get own proxy object through context and call 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
    @Async
    public void testAsyncTask() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("Asynchronous task execution completed!");
    }

}

Turn on cglib proxy and get the Spring proxy class manually, so as to call asynchronous methods under the same category. First, annotate the startup class with @ EnableAspectJAutoProxy(exposeProxy = true). 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 is the key!!!
        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

 

The use scenarios of the two are different. Asynchronous requests are used to solve the pressure caused by concurrent requests on the server, so as to improve the throughput of requests. Asynchronous calls are used to do some non mainline processes without real-time calculation and response tasks, such as synchronous logs to kafka for log analysis.

 

Asynchronous requests are always waiting for response and need to return results to the client; while asynchronous calls are often returned to the client immediately to respond and complete the whole request. As for the task of asynchronous calls, the background can run slowly, and the client will not mind.

 

Four, summary

 

Asynchronous request and asynchronous call are almost used here. If you have any problems, please point out more. This article mentioned the dynamic agent, and the principle of Aop in spring is dynamic agent. In the future, we will explain the dynamic agent in detail, and hope to support it more.

Tags: Java Spring Redis MongoDB

Posted on Tue, 17 Mar 2020 01:59:05 -0700 by m00ch0