1はじめに
この記事を読む前に、タスクの非同期処理の経験に@Asyncアノテーションを使用したと思います。プロジェクト開発プロセスでは、非メインプロセス、非リアルタイム、および時間のかかるタスクの場合、非同期処理が頻繁に実行されます。メインプロセスに影響を与えますが、メインプロセスの応答時間も改善します。
非同期処理に@Asyncアノテーションを使用する過程で、次のような多くの落とし穴も踏んだと思います。共有スレッドプールのためにタスクが非同期で実行されない、タスク間の相互影響、非同期タスクが異常である、対処方法がわからないなど。待つ。今日はその本当の色を理解して、次回問題が発生したときにうまく仕事ができ、慌てずに始められないようにします。
2.クエスト
2.1実装原則
2.1.1非同期アノテーションポストプロセッサを探す
プロジェクトで@Asyncアノテーションを使用して非同期タスクを実行するには、非同期関数を手動で有効にする必要があることを知っておく必要があります。これを有効にする方法は、@ EnableAsyncを追加することです。
@SpringBootApplication
@EnableAsync
public class SpringBootAsyncApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAsyncApplication.class, args);
}
}
非同期機能は@EnableAsyncアノテーションを介してオンにできるため、このアノテーションは私たちの探索への入り口です
@EnableAsyncアノテーションを入力すると、別の使い慣れたアノテーション@Importが表示されます。このアノテーションの機能は、プログラム内の関連する関数に対応する構成クラスを導入することです。
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {}
AsyncConfigurationSelectorをクリックすると、今回はProxyAsyncConfiguration構成クラスが導入されていることがわかります。
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
ProxyAsyncConfiguration構成クラスを入力します
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
bpp.configure(this.executor, this.exceptionHandler);
Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
bpp.setAsyncAnnotationType(customAsyncAnnotation);
}
bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
return bpp;
}
AsyncAnnotationBeanPostProcessorなどのBeanがProxyAsyncConfiguration構成クラスで宣言されていることがわかります。文字通りの意味から、Beanが非同期処理の主役である必要があることも推測できます。次に、主人公が何をするかを見てみましょう。
AsyncAnnotationBeanPostProcessorと入力すると、このクラスがBeanFactoryAware、BeanPostProcessor、Beanのライフサイクルに密接に関連する2つのインターフェイスを実装していることがわかります。Beanのライフサイクル特性から、BeanFactoryAwareインターフェイスの実装メソッドがBeanPostProcessorインターフェイスの実装メソッドの前に実行されていることがわかります。
2.1.2BeanFactoryAwareの実装
2.1.2.1アスペクトを定義する
@Override
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
// 定义切面
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
if (this.asyncAnnotationType != null) {
advisor.setAsyncAnnotationType(this.asyncAnnotationType);
}
advisor.setBeanFactory(beanFactory);
this.advisor = advisor;
}
アスペクトオブジェクトはsetBeanFactory()実装メソッドで定義されています。アスペクトという言葉を見ると、それに関連する2つの概念、連絡先と通知がすぐに頭に浮かぶと思います。
- カットポイント:カットの目標を宣言するために使用されます
- 通知:カットインターゲットの対応する処理
2.1.3カットポイントを定義する
Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
asyncAnnotationTypes.add(Async.class);
try {
asyncAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
// If EJB 3.1 API not present, simply ignore.
}
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {
ComposablePointcut result = null;
for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {
// 定义在类上标注@Async、@Asynchronous注解的切点
Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
// 定义在方法上标注@Async、@Asynchronous注解的切点
Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);
if (result == null) {
result = new ComposablePointcut(cpc);
}
else {
result.union(cpc);
}
result = result.union(mpc);
}
return (result != null ? result : Pointcut.TRUE);
}
2.1.4通知を定義する
protected Advice buildAdvice(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
// 定义通知
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
通知は最終的な実装であり、非常に重要な部分でもあります。通知は非常に重要であるため、特定の実装を確認する必要があります。
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 获取异步任务线程池
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
// 定义Callable对象
Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future<?>) result).get();
}
}
...
return null;
};
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
// 异步任务的返回值类型是CompletableFuture
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
}
catch (Throwable ex) {
throw new CompletionException(ex);
}
}, executor);
}
// 异步任务的返回值类型是ListenableFuture
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
}
// 异步任务的返回值类型是Future
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
// 否则交由线程池来处理,没有返回值
else {
executor.submit(task);
return null;
}
}
通知の具体的な実装は次のとおりです。
- 最初のステップは、非同期タスクを実行するための非同期タスクスレッドプールを取得することです
- Callableを使用してターゲットメソッドをラップします
- 非同期非同期タスクを実行し、さまざまな戻り値タイプに従って対応する処理を実行します
通知を通じて、非同期タスクの最終的な実現の原則を理解できますが、非同期タスクを実行するために通知を通知する方法について、まだ疑問がある場合があります。
わかりません。上記のBeanPostProcessorインターフェイスを覚えていますか。具体的な実装を見てみましょう。
2.1.3BeanPostProcessorの実装
BeanPostProcessorインターフェイスに言及すると、その処理メソッドがプロキシの生成など、Beanで何らかの処理を実行する必要があることにすぐに気付くはずです。
基本的な知識が得られたら、対応する後処理の実装をここで見てみましょう。
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 判断当前Bean是否满足之前定义的切点,如果满足则生成代理对象
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}
// No proxy needed.
return bean;
}
BeanPostProcessorの後処理は、カットポイントを満たすBeanのプロキシを生成するために使用されます。ターゲットメソッドが呼び出されると、notify()メソッドが実行されます。
この時点で、非同期実装の原則は終わりました。実際、原則は非常に単純です。連絡先と通知を定義する必要があります。ターゲットメソッドの拡張を実現したい場合は、当然、リバースプロキシを考えます。最後に、元のBeanを変更する方法を教えてください。この時点で、Beanのライフサイクルに関連するBeanPostProcessorインターフェイスに接続する必要があります
2.2スレッドプールの使用
スレッドプールは依然として非常に重要です。不適切な使用は、メモリオーバーフロー、無制限のスレッド作成、ビジネス間の相互影響など、予期しない問題を引き起こす可能性があります。
* <p>デフォルトでは、Springは関連するスレッドプール定義を検索します:*コンテキスト内の一意の{@linkorg.springframework.core.task.TaskExecutor}Beanまたは*または{@ linkjava.util。それ以外の場合は、「taskExecutor」という名前のconcurrent.Executor} Bean。复制代码
公式ドキュメントによると、Springは一意のTaskExecutorまたは「taskExecutor」という名前のBeanをスレッドプールとしてコンテキストから取得することがわかっています。デフォルトのスレッドプールは、TaskExecutionAutoConfiguration自動構成クラスで定義されています。デフォルトのスレッドプール関連の構成は次のとおりです。
デフォルトのスレッドプールのキューサイズとスレッドの最大数は両方ともIntegerの最大値であり、明らかにシステムに特定のリスクを残していることがわかります。したがって、非同期タスクごとにスレッドプールをカスタマイズしてから、@ Async()を使用する必要があります。注釈は、対応するスレッドプールのBean名を示します
2.3例外処理
非同期タスクの例外処理では、デフォルトでログ情報のみが出力され、追加の処理は行われません。公式ドキュメントにも関連する手順があります。
さらに、* {@code void}の戻り値の型を持つアノテーション付きメソッドは、呼び出し元に例外を送信できません。デフォルトでは、*このようなキャッチされない例外はログに記録されるだけです。复制代码
SimpleAsyncUncaughtExceptionHandlerは、非同期タスクの例外処理のデフォルトの実装です。例外処理をカスタマイズする場合は、AsyncConfigurerインターフェイスのみが必要です。
2.4戻り値のタイプ
戻り値の種類については、まず公式の説明をご覧ください
* <p>ターゲットメソッドのシグネチャに関しては、すべてのパラメータタイプがサポートされています。*ただし、戻り値の型は{@codevoid}または*{@linkjava.util.concurrent.Future}のいずれかに制限されます。後者の場合、非同期タスクとのより豊富な相互作用を可能にする*より具体的な{@ linkorg.springframework.util.concurrent.ListenableFuture}または*{@linkjava.util.concurrent.CompletableFuture}タイプを宣言できます。 *さらなる処理ステップでの即時構成用。复制代码
公式の説明から、戻り値タイプは4つのタイプのみをサポートしていることがわかります。
- ボイド
- 未来
- ListenableFuture
- CompletableFuture
特定のソースコードを見てみましょう
公式の説明やソースコードの分析に関係なく、非同期タスクは4つのリターンタイプしかサポートしていないと結論付けることができます。将来的には、Stringリターンタイプがnullを返す理由を他の人に尋ねる必要はありません。
著者:レンガを動かす黒人と白人の労働者
リンク:https://juejin.im/post/6858854987280809997
出典:ナゲット