サーブレットコンテナーの構成
DispatcherServletおよびweb.xmlのすべてのフィルターに追加します
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を通過する必要があります
次に、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>
注:フィルターがアノテーション構成に基づいている場合は、@ WebFilter(asyncSupported = true、dispatcherTypes = {DispatcherType.ASYNC、DispatcherType.REQUEST})を追加する必要があります。
サーブレット3を使用する場合(たとえば、WebApplicationInitializerによるJavaベースの構成)、web.xmlを使用する場合と同様に、「asyncSupported」フラグとASYNCスケジューラタイプも設定する必要があります。すべての構成を簡略化するには、AbstractDispatcherServletInitializerまたはより適切なAbstractAnnotationConfigDispatcherServletInitializerを拡張して、これらのオプションを自動的に設定し、Filterインスタンスを簡単に登録できるようにすることを検討してください。
Spring MVC構成
MVC Java構成とMVC名前空間は、非同期要求処理を構成するためのオプションを提供します。WebMvcConfigurerにはメソッドconfigureAsyncSupportがあり、<mvc:annotation-driven>にはメソッドがあります
これらにより、非同期リクエストのデフォルトのタイムアウト値を設定できます。設定されていない場合は、基盤となるサーブレットコンテナーに依存します(たとえば、Tomcatでは10秒)。コントローラーメソッドから返されたCallableインスタンスを実行するようにAsyncTaskExecutorを構成することもできます。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:非同期要求処理がタイムアウトするまでの時間(ミリ秒単位)を指定します。サーブレット3では、タイムアウトはメインのリクエスト処理スレッドが終了した後に開始され、リクエストが終了して再びディスパッチされて同時結果をさらに処理すると終了します。この値が設定されていない場合は、基盤となる実装のデフォルトのタイムアウトを使用します。たとえば、サーブレット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では、サーブレット3に基づく非同期リクエスト処理が導入されました。これで、コントローラーメソッドは通常のように値を返す必要はありませんが、java.util.concurrent.Callableを返し、Spring MVCマネージスレッドから戻り値を生成できます。同時に、メインサーブレットコンテナスレッドを終了して解放し、他のリクエストを処理できるようにします。Spring MVCは、TaskExecutorを使用して別のスレッドでCallableを呼び出します。Callableが戻ると、リクエストはサーブレットコンテナにディスパッチされ、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();
}
}
サーブレット3.0の非同期リクエスト処理の特性を理解していない場合、これを理解することは困難です。この状況について詳しく知ることは間違いなく役立ちます。潜在的なメカニズムに関するいくつかの基本的な事実は次のとおりです。
-
request.startAsync()を呼び出すことにより、ServletRequestを非同期モードにすることができます。これの主な効果は、サーブレットとすべてのフィルターが終了できることですが、応答は開いたままなので、処理を後で完了することができます。
-
request.startAsync()を呼び出すと、非同期処理をさらに制御するために使用できるAsyncContextが返されます。たとえば、サーブレットAPI転送に似たメソッドディスパッチを提供しますが、アプリケーションはサーブレットコンテナスレッドでリクエスト処理を再開できます。
-
ServletRequestは、現在のDispatcherTypeへのアクセスを提供します。これを使用して、初期要求の処理、非同期ディスパッチ、転送、およびその他のディスパッチャタイプを区別できます。
上記を考慮して、以下は非同期リクエスト処理にCallableを使用するイベントシーケンスです。
- コントローラはCallableを返します。
- Spring MVCは非同期処理を開始し、別のスレッドで処理するためにCallableをTaskExecutorに送信します。
- DispatcherServletとすべてのフィルターはServletコンテナースレッドを終了しますが、応答は開いたままです
- Callableは結果を生成し、Spring MVCはリクエストをサーブレットコンテナにディスパッチして処理を再開します。
- DispatcherServletを再度呼び出し、Callableによって非同期に生成された結果を使用して処理を再開します。
イベントのDeferredResult要求シーケンス
- コントローラーはDeferredResultを返し、アクセス可能なメモリキューまたはリストに保存します。
- Spring MVCは非同期処理を開始します
- DispatcherServletおよびすべての構成済みフィルターは、要求処理スレッドを終了しますが、応答は開いたままです。
- アプリケーションはスレッドからDeferredResultを設定し、Spring MVCはリクエストをサーブレットコンテナーにディスパッチします。
- DispatcherServletを再度呼び出し、非同期に生成された結果で処理を再開します。