Processamento de solicitação assíncrona de primavera

Configuração de contêiner de servlet

Adicione ao DispatcherServlet e a todos os filtros em web.xml

Para aplicativos configurados com web.xml, atualize para a versão 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

Deve passar pelo web.xml Os elementos filho </ async-supported> true permitem o suporte assíncrono no DispatcherServlet. Além disso, todos os filtros envolvidos no processamento de solicitações assíncronas devem ser configurados para suportar o tipo de agendador ASYNC. Deve ser seguro ativar o tipo de agendador ASYNC para todos os filtros fornecidos pela estrutura Spring, porque eles geralmente estendem o OncePerRequestFilter e podem verificar em tempo de execução se o filtro precisa ser incluído no agendamento assíncrono.

Aqui estão algumas configurações de web.xml de amostra:

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

Nota: Se o seu Filtro for baseado na configuração da anotação precisar ser adicionado da seguinte forma, @WebFilter (asyncSupported = true, dispatcherTypes = {DispatcherType.ASYNC, DispatcherType.REQUEST})

Se você usar o Servlet 3 (por exemplo, configuração baseada em Java via WebApplicationInitializer), também precisará definir o sinalizador "asyncSupported" e o tipo de agendador ASYNC, assim como o web.xml. Para simplificar toda a configuração, considere estender AbstractDispatcherServletInitializer ou melhor AbstractAnnotationConfigDispatcherServletInitializer, que definirá automaticamente essas opções e facilitará o registro de instâncias de filtro.

Configuração do Spring MVC

A configuração do MVC Java e o namespace do MVC fornecem opções para configurar o processamento de solicitações assíncronas. O WebMvcConfigurer possui o método configureAsyncSupport e <mvc: acionado por anotação> possui um Elemento filho

Isso permite configurar o valor de tempo limite padrão para solicitações assíncronas, se não estiver definido, depende do contêiner do Servlet subjacente (por exemplo, 10 segundos no Tomcat). Você também pode configurar o AsyncTaskExecutor para executar a instância Callable retornada do método do controlador. É altamente recomendável configurar essa propriedade, porque o Spring MVC usa SimpleAsyncTaskExecutor por padrão. Ele não reutiliza threads, portanto, não é recomendado para ambientes de produção. A configuração do MVC Java e o namespace do MVC também permitem registrar as instâncias CallableProcessingInterceptor e 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>

tempo limite padrão: especifica o tempo (em milissegundos) antes do tempo limite do processamento da solicitação assíncrona. No Servlet 3, o tempo limite inicia após o término do encadeamento de processamento da solicitação principal e termina quando a solicitação termina e é despachado novamente para processar ainda mais os resultados simultâneos. Se esse valor não estiver definido, use o tempo limite padrão da implementação subjacente, por exemplo, use o Servlet 3 para executar no Tomcat por 10 segundos.

Configuração 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;
    }

}

Configurar processamento de solicitação assíncrona

O Spring MVC 3.2 introduziu o processamento de solicitações assíncronas com base no Servlet 3. Agora, o método do controlador não precisa retornar o valor como de costume, mas pode retornar java.util.concurrent.Callable e gerar o valor de retorno a partir do encadeamento gerenciado do Spring MVC. Ao mesmo tempo, saia e libere o encadeamento principal do contêiner do Servlet e permita que ele lide com outras solicitações. O Spring MVC usa o TaskExecutor para chamar o Callable em um thread separado.Quando o Callable retorna, a solicitação é enviada de volta ao contêiner do Servlet para retomar o processamento usando o valor retornado pelo Callable. Este é um exemplo deste método do controlador:

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

                return "someView";
            }
        };
    }

Outra opção é que o método controller retorne uma instância de DeferredResult. Nesse caso, o valor de retorno também será gerado a partir de qualquer encadeamento, ou seja, um encadeamento que não seja gerenciado pelo Spring MVC. Por exemplo, os resultados podem ser gerados em resposta a determinados eventos externos (como mensagens JMS, tarefas agendadas etc.). Aqui está um exemplo desse método de controlador:

Use a fila de bloqueio para processar solicitações de usuário de forma assíncrona

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


}

Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

Se você não entender as características do processamento de solicitações assíncronas do Servlet 3.0, é difícil entender isso. Saber mais sobre essa situação definitivamente ajudará. Aqui estão alguns fatos básicos sobre possíveis mecanismos:

  • Você pode colocar ServletRequest no modo assíncrono chamando request.startAsync (). O principal efeito disso é que o Servlet e todos os filtros podem sair, mas a resposta permanecerá aberta para que o processamento possa ser concluído posteriormente

  • Chamar request.startAsync () retorna um AsyncContext, que pode ser usado para controlar ainda mais o processamento assíncrono. Por exemplo, ele fornece envio de método, semelhante ao encaminhamento da API do Servlet, mas permite que os aplicativos continuem o processamento de solicitações no encadeamento do contêiner do Servlet.

  • ServletRequest fornece acesso ao DispatcherType atual, que pode ser usado para distinguir entre processar pedidos iniciais, envio assíncrono, encaminhamento e outros tipos de expedidor.

Considerando o acima, a seguir está uma sequência de eventos usando Callable para processamento de solicitação assíncrona:

  • O controlador retorna Callable.
  • O Spring MVC inicia o processamento assíncrono e envia o Callable ao TaskExecutor para processamento em um thread separado.
  • DispatcherServlet e todos os filtros saem do encadeamento do contêiner Servlet, mas a resposta permanece aberta
  • Callable produz resultados e o Spring MVC envia a solicitação de volta ao contêiner Servlet para retomar o processamento.
  • Ligue para DispatcherServlet novamente e retome o processamento usando os resultados gerados pelo Callable de forma assíncrona.

Sequência de eventos de solicitação DeferredResult

  • O controlador retorna o DeferredResult e o salva em alguma fila ou lista de memória, onde pode ser acessado
  • Spring MVC inicia processamento assíncrono
  • DispatcherServlet e todos os filtros configurados saem do encadeamento de processamento de solicitações, mas a resposta permanece aberta.
  • O aplicativo define o DeferredResult a partir de um encadeamento e o Spring MVC envia a solicitação de volta ao contêiner do Servlet.
  • Ligue para DispatcherServlet novamente e continue o processamento com os resultados gerados de forma assíncrona.

Insira a descrição da imagem aqui

Acho que você gosta

Origin www.cnblogs.com/niugang0920/p/12689210.html
Recomendado
Clasificación