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