Spring asynchronous request processing

Servlet container configuration

Add to DispatcherServlet and all filters in web.xml

For applications configured with web.xml, be sure to update to version 3.0:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

</web-app

Must go through web.xml true </ async-supported> child elements enable asynchronous support on DispatcherServlet. In addition, all Filters involved in asynchronous request processing must be configured to support the ASYNC scheduler type. It should be safe to enable the ASYNC scheduler type for all filters provided by the Spring framework, because they usually extend OncePerRequestFilter and can check at runtime whether the filter needs to be included in the asynchronous scheduler.

Here are some sample web.xml configurations:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
    
<filter>
   <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
   <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
   <async-supported>true</async-supported>
</filter>
<filter-mapping>
     <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
     <url-pattern>/*</url-pattern>
     <dispatcher>REQUEST</dispatcher>
     <dispatcher>ASYNC</dispatcher>
</filter-mapping>
<servlet>
     <servlet-name>dispatcher</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value/>
        </init-param>
        <load-on-startup>2</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
     <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
     </servlet-mapping>
</web-app>

Note: If your Filter is based on annotation configuration needs to be added as follows, @WebFilter (asyncSupported = true, dispatcherTypes = {DispatcherType.ASYNC, DispatcherType.REQUEST})

If you use Servlet 3 (for example, Java-based configuration via WebApplicationInitializer), you also need to set the "asyncSupported" flag and the ASYNC scheduler type, just like using web.xml. To simplify all configuration, please consider extending AbstractDispatcherServletInitializer, or better AbstractAnnotationConfigDispatcherServletInitializer, which will automatically set these options and make it easy to register Filter instances.

Spring MVC configuration

The MVC Java configuration and MVC namespace provide options for configuring asynchronous request processing. WebMvcConfigurer has method configureAsyncSupport, and <mvc: annotation-driven> has one Child element

These allow configuring the default timeout value for asynchronous requests, if not set, it depends on the underlying Servlet container (for example, 10 seconds on Tomcat). You can also configure AsyncTaskExecutor to execute the Callable instance returned from the controller method. It is strongly recommended to configure this property, because Spring MVC uses SimpleAsyncTaskExecutor by default. It does not reuse threads, so it is not recommended for production environments. The MVC Java configuration and MVC namespace also allow you to register CallableProcessingInterceptor and DeferredResultProcessingInterceptor instances.

   <bean id="threadPoolTaskExecutor"
          class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
       <!--最小线程数 -->
        <property name="corePoolSize" value="5" />
       <!--最大线程数 -->
        <property name="maxPoolSize" value="10" />
       <!--缓冲队列大小 -->
        <property name="queueCapacity" value="50" />
       <!--线程池中产生的线程名字前缀 -->
        <property name="threadNamePrefix" value="Async-Task-" />
        <!--线程池中空闲线程的存活时间单位秒 -->
        <property name="keepAliveSeconds" value="30" />
    </bean>
    <aop:aspectj-autoproxy/>
    <mvc:annotation-driven >
        <mvc:async-support default-timeout="10000" task-executor="threadPoolTaskExecutor"/>
    </mvc:annotation-driven>

default-timeout: Specifies the time (in milliseconds) before the asynchronous request processing times out. In Servlet 3, the timeout starts after the main request processing thread exits, and ends when the request ends and is dispatched again to further process the simultaneous results. If this value is not set, use the default timeout of the underlying implementation, for example, use Servlet 3 to run on Tomcat for 10 seconds.

Java Config configuration

/**
 * 异步配置类
 */
@Configuration
public class AsynWebConfig implements WebMvcConfigurer {

    //配置自定义TaskExecutor
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(60 * 1000L);
        configurer.registerCallableInterceptors(timeoutInterceptor());
        configurer.setTaskExecutor(threadPoolTaskExecutor());
    }

    //异步处理拦截
    @Bean
    public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }
    //异步线程池
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
        t.setCorePoolSize(5);
        t.setMaxPoolSize(10);
        t.setThreadNamePrefix("NEAL");
        return t;
    }

}

Configure asynchronous request processing

Spring MVC 3.2 introduced asynchronous request processing based on Servlet 3. Now, the controller method does not need to return the value as usual, but can return java.util.concurrent.Callable and generate the return value from the Spring MVC managed thread. At the same time, exit and release the main Servlet container thread and allow it to handle other requests. Spring MVC uses TaskExecutor to call Callable in a separate thread. When Callable returns, the request is dispatched back to the Servlet container to resume processing using the value returned by Callable. This is an example of this controller method:

    @PostMapping(value = "v1/files.do")
    public Callable<String> processUpload(final MultipartFile file) {
        return new Callable<String>() {
            @Override
            public String call() throws Exception {

                return "someView";
            }
        };
    }

Another option is that the controller method returns an instance of DeferredResult. In this case, the return value will also be generated from any thread, that is, a thread that is not managed by Spring MVC. For example, the results may be generated in response to certain external events (such as JMS messages, scheduled tasks, etc.). Here is an example of such a controller method:

Use blocking queue to process user requests asynchronously

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.UUID;

/**
 * @author Created by niugang on 2020/4/2/20:00
 */
@RestController
@Slf4j
public class DeferredResultUserInfoSaveController {

    private final SimilarQueueHolder similarQueueHolder;

    @Autowired
    public DeferredResultUserInfoSaveController(SimilarQueueHolder similarQueueHolder) {
        this.similarQueueHolder = similarQueueHolder;
    }

    @PostMapping("/deferred/result")
    public DeferredResult<Object> deferredResultHelloWolrd(@RequestBody UserInfo userInfo ) {

        printlnThread("主线程--deferredResultHelloWolrd开始执行");
        //声明异步DeferredResult
        DeferredResult<Object> deferredResult = new DeferredResult<>();
        userInfo.setId(UUID.randomUUID().toString());
        deferredResult.setResult(userInfo);
        //模拟放入消息队列
        boolean offer = similarQueueHolder.getBlockingDeque().offer(deferredResult);
        if(!offer){
            log.info("添加任务到队列:{}",offer);
            DeferredResult<Object> deferredResult1 = new DeferredResult<>();
            deferredResult1.setResult("限流了稍后重试");
            return deferredResult1;
        }
        log.info("添加任务到队列:{}",offer);
        printlnThread("主线程--deferredResultHelloWolrd结束执行");
        return deferredResult;
    }



    /**
     * 打印当前线程
     * @param object object
     */
    private void printlnThread(Object object) {
        String threadName = Thread.currentThread().getName();
        log.info("HelloWorldAsyncController[{}]:{} ",threadName,object) ;
    }



}
import lombok.Data;

/**
 * @author Created by niugang on 2020/4/2/20:04
 */
@Data
public class UserInfo {

    private  String name;


    private int  age;

    private String id;
}

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 模拟消息队列
 *
 * @author Created by niugang on 2020/4/2/19:59
 */
@Component
public class SimilarQueueHolder {

    /**
     * 创建容量为5的阻塞队列
     */
    private static BlockingQueue<DeferredResult<Object>> blockingDeque = new ArrayBlockingQueue<>(5);

    public BlockingQueue<DeferredResult<Object>> getBlockingDeque() {
        return blockingDeque;
    }

}

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.TimeUnit;

/**
 * 使用监听器来模拟消息队列处理
 * @author Created by niugang on 2020/4/2/20:00
 */
@Configuration
@Slf4j
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {

    private final SimilarQueueHolder similarQueueHolder;

    @Autowired
    public QueueListener(SimilarQueueHolder similarQueueHolder) {
        this.similarQueueHolder = similarQueueHolder;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        new Thread(()->{
            while(true) {
                try {
                    //从队列中取出DeferredResult
                    DeferredResult<Object> deferredResult = similarQueueHolder.getBlockingDeque().take();
                    log.info("开始DeferredResult异步处理");
                    //模拟处理时间
                    TimeUnit.SECONDS.sleep(3);
                    log.info("用户信息:{}",deferredResult.getResult());
                    log.info("结束DeferredResult异步处理");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


}

Insert picture description here
Insert picture description here

If you don't understand the characteristics of Servlet 3.0 asynchronous request processing, it is difficult to understand this. Knowing more about this situation will definitely help. Here are some basic facts about potential mechanisms:

  • You can place ServletRequest in asynchronous mode by calling request.startAsync (). The main effect of this is that the Servlet and all filters can exit, but the response will remain open so that the processing can be completed later

  • Calling request.startAsync () returns an AsyncContext, which can be used to further control asynchronous processing. For example, it provides method dispatch, similar to Servlet API forwarding, but it allows applications to resume request processing on the Servlet container thread.

  • ServletRequest provides access to the current DispatcherType, which can be used to distinguish between processing initial requests, asynchronous dispatch, forwarding, and other dispatcher types.

Considering the above, the following is an event sequence using Callable for asynchronous request processing:

  • The controller returns Callable.
  • Spring MVC starts asynchronous processing and submits Callable to TaskExecutor for processing in a separate thread.
  • DispatcherServlet and all filters exit the Servlet container thread, but the response remains open
  • Callable produces results, and Spring MVC dispatches the request back to the Servlet container to resume processing.
  • Call DispatcherServlet again and resume processing using the results generated by Callable asynchronously.

DeferredResult request sequence of events

  • The controller returns the DeferredResult and saves it in some memory queue or list, where it can be accessed
  • Spring MVC starts asynchronous processing
  • DispatcherServlet and all configured filters exit the request processing thread, but the response remains open.
  • The application sets the DeferredResult from a thread, and Spring MVC dispatches the request back to the Servlet container.
  • Call DispatcherServlet again, and resume processing with the results generated asynchronously.

Insert picture description here

Guess you like

Origin www.cnblogs.com/niugang0920/p/12689210.html