zoukankan      html  css  js  c++  java
  • Spring异步请求处理

    Servlet容器配置

    在web.xml中对DispatcherServlet和所有filter添加

    对于配置了web.xml的应用程序,请确保更新至版本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
    

    必须通过web.xml中的 true </ async-supported>子元素在DispatcherServlet上启用异步支持。 另外,必须将参与异步请求处理的所有Filter配置为支持ASYNC调度程序类型。 为Spring框架提供的所有过滤器启用ASYNC调度程序类型应该是安全的,因为它们通常扩展了OncePerRequestFilter,并且可以在运行时检查是否需要将过滤器包含在异步调度中。

    以下是一些示例web.xml配置:

    <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>
    

    注意:如果你的Filter是基于注解配置的需要增加如下,@WebFilter(asyncSupported = true,dispatcherTypes = {DispatcherType.ASYNC,DispatcherType.REQUEST})

    如果使用Servlet 3(例如通过WebApplicationInitializer的基于Java的配置),则还需要设置“ asyncSupported”标志以及ASYNC调度程序类型,就像使用web.xml一样。为了简化所有配置,请考虑扩展AbstractDispatcherServletInitializer,或更好的AbstractAnnotationConfigDispatcherServletInitializer,它会自动设置这些选项并使注册Filter实例非常容易。

    Spring MVC 配置

    MVC Java配置和MVC名称空间提供用于配置异步请求处理的选项。 WebMvcConfigurer具有方法configureAsyncSupport,而<mvc:annotation-driven>有一个子元素

    这些允许配置用于异步请求的默认超时值,如果未设置,则取决于底层的Servlet容器(例如,在Tomcat上为10秒)。 您还可以配置AsyncTaskExecutor来执行从控制器方法返回的Callable实例。 强烈建议配置此属性,因为默认情况下,Spring MVC使用SimpleAsyncTaskExecutor。 它不会重复使用线程,因此不建议用于生产环境。MVC Java配置和MVC命名空间还允许您注册CallableProcessingInterceptor和DeferredResultProcessingInterceptor实例。

       <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:指定异步请求处理超时之前的时间(以毫秒为单位)。在Servlet 3中,超时从主要请求处理线程退出后开始,到请求结束时结束再次分派以进一步处理同时产生的结果。 如果未设置此值,使用底层实现的默认超时时间,例如 使用Servlet 3在Tomcat上运行10秒。

    Java Config配置

    /**
     * 异步配置类
     */
    @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;
        }
    
    }
    

    配置异步请求处理

    Spring MVC 3.2引入了基于Servlet 3的异步请求处理。 现在,控制器方法无需像往常一样返回值,而是可以返回java.util.concurrent.Callable并从Spring MVC托管线程产生返回值。 同时,退出并释放主要的Servlet容器线程,并允许其处理其他请求。 Spring MVC借助TaskExecutor在一个单独的线程中调用Callable,当Callable返回时,该请求被分派回Servlet容器,以使用Callable返回的值恢复处理。 这是这种控制器方法的示例:

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

    另一个选项是控制器方法返回DeferredResult的一个实例。在这种情况下,返回值也会从任何线程中产生,即一个不是由Spring MVC管理的线程。例如,可能会在响应某些外部事件(如JMS消息、调度任务等)时生成结果。下面是这样一个控制器方法的例子:

    使用阻塞队列异步处理用户请求,超过阻塞队列容量提示限流

    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();
        }
    
    
    }
    

    在这里插入图片描述
    在这里插入图片描述

    如果不了解Servlet 3.0异步请求处理特性,就很难理解这一点。多了解这方面的情况肯定会有帮助。以下是关于潜在机制的一些基本事实:

    • 可以通过调用request.startAsync()将ServletRequest置于异步模式。 这样做的主要效果是Servlet以及所有过滤器都可以退出,但响应将保持打开状态,以便以后可以完成处理

    • 调用request.startAsync()返回AsyncContext,该AsyncContext可用于进一步控制异步处理。 例如,它提供了方法分派,类似于Servlet API的转发,但它允许应用程序恢复Servlet容器线程上的请求处理。

    • ServletRequest提供对当前DispatcherType的访问,该访问可用于区分处理初始请求,异步分派,转发和其他分派器类型。

    考虑到上述内容,以下是使用Callable进行异步请求处理的事件序列:

    • 控制器返回Callable。
    • Spring MVC开始异步处理,并将Callable提交给TaskExecutor在单独的线程中进行处理。
    • DispatcherServlet和所有Filter退出Servlet容器线程,但响应保持打开状态
    • Callable产生结果,Spring MVC将请求分派回Servlet容器以恢复处理。
    • 再次调用DispatcherServlet,并使用Callable异步生成的结果恢复处理。

    DeferredResult请求的事件序列

    • 控制器返回DeferredResult并将其保存在一些内存队列或列表中,可以在其中访问
    • Spring MVC开始异步处理
    • DispatcherServlet和所有已配置的Filter退出请求处理线程,但响应保持打开状态。
    • 应用程序从某个线程设置DeferredResult,Spring MVC将请求分派回Servlet容器。
    • 再次调用DispatcherServlet,并以异步产生的结果恢复处理。

    在这里插入图片描述

  • 相关阅读:
    SpringBoot + CXF快速实现SOAP WebService(支持Basic Auth)
    利用iptables做端口转发
    artDialog测试
    jquery的常用ajax操作
    通过委托让缓存操作更优雅
    Jquery取得iframe中元素的几种方法
    jQuery选择器大全
    常用JS汇总
    firefox广告拦截插件
    easyUI删除行的操作
  • 原文地址:https://www.cnblogs.com/niugang0920/p/12689210.html
Copyright © 2011-2022 走看看