1. Обзор
В этом руководстве мы рассмотрим поддержку асинхронного выполнения в Spring и аннотацию @Async
.
Проще говоря, аннотирование метода компонента с помощью @Async
заставит его выполняться в отдельном потоке. Другими словами, вызывающая сторона не будет ждать завершения вызываемого метода.
Одним из интересных аспектов Spring является то, что поддержка событий в фреймворке также поддерживает асинхронную обработку , если это необходимо.
2. Включите асинхронную поддержку ``
Начнем с включения асинхронной обработки с конфигурацией Java.
Мы сделаем это, добавив @EnableAsync
в класс конфигурации:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
Достаточно включить аннотацию. Но есть и несколько простых вариантов настройки:
аннотация —
по умолчанию@EnableAsync
обнаруживает аннотацию Spring@Async
и EJB 3.1javax.ejb.Asynchronous
. Мы можем использовать эту опцию для обнаружения других пользовательских типов аннотаций.mode
указывает типрекомендации
, которую следует использовать — на основе прокси-сервера JDK или на основе AspectJ.proxyTargetClass
указывает типпрокси
, который следует использовать — CGLIB или JDK. Этот атрибут действует только в том случае, если длярежима
установлено значениеAdviceMode.PROXY
.порядок
устанавливает порядок, в котором должен применятьсяAsyncAnnotationBeanPostProcessor .
По умолчанию он запускается последним, чтобы учесть все существующие прокси.
Мы также можем включить асинхронную обработку с конфигурацией XML , используя пространство имен задач :
<task:executor id="myexecutor" pool-size="5" />
<task:annotation-driven executor="myexecutor"/>
3. Аннотация @Async
Во-первых, давайте пройдемся по правилам. @Async
имеет два ограничения:
- Он должен применяться только к
общедоступным
методам. - Самовызов — вызов асинхронного метода из того же класса — не сработает.
Причины просты: метод должен быть общедоступным
, чтобы его можно было проксировать. И самовызов не работает, потому что он обходит прокси-сервер и напрямую вызывает базовый метод.
3.1. Методы с возвращаемым типом Void
Это простой способ настроить метод с возвращаемым типом void для асинхронного запуска:
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "
+ Thread.currentThread().getName());
}
3.2. Методы с возвращаемым типом
Мы также можем применить @Async
к методу с возвращаемым типом, обернув фактический возврат в Future:
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("Execute method asynchronously - "
+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
Spring также предоставляет класс AsyncResult
, реализующий Future
. Мы можем использовать это для отслеживания результата выполнения асинхронного метода.
Теперь давайте вызовем описанный выше метод и получим результат асинхронного процесса с помощью объекта Future .
public void testAsyncAnnotationForMethodsWithReturnType()
throws InterruptedException, ExecutionException {
System.out.println("Invoking an asynchronous method. "
+ Thread.currentThread().getName());
Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();
while (true) {
if (future.isDone()) {
System.out.println("Result from asynchronous process - " + future.get());
break;
}
System.out.println("Continue doing something else. ");
Thread.sleep(1000);
}
}
4. Исполнитель
По умолчанию Spring использует SimpleAsyncTaskExecutor
для асинхронного запуска этих методов. Но мы можем переопределить значения по умолчанию на двух уровнях: уровне приложения или уровне отдельного метода.
4.1. Переопределить исполнителя на уровне метода
Нам нужно объявить требуемый исполнитель в классе конфигурации:
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
Затем мы должны указать имя исполнителя в качестве атрибута в @Async
:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - "
+ Thread.currentThread().getName());
}
4.2. Переопределить исполнителя на уровне приложения
Класс конфигурации должен реализовать интерфейс AsyncConfigurer
. Итак, он должен реализовать метод getAsyncExecutor()
. Здесь мы вернем исполнителя для всего приложения. Теперь он становится исполнителем по умолчанию для запуска методов, аннотированных с помощью @Async
:
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
5. Обработка исключений
Когда тип возвращаемого значения метода — Future
, обработка исключений проста. Метод Future.get()
вызовет исключение.
Но если возвращаемый тип — void
, исключения не будут распространяться на вызывающий поток. Итак, нам нужно добавить дополнительные конфигурации для обработки исключений.
Мы создадим собственный обработчик асинхронных исключений, реализуя интерфейс AsyncUncaughtExceptionHandler
. Метод handleUncaughtException()
вызывается при наличии неперехваченных асинхронных исключений:
public class CustomAsyncExceptionHandler
implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(
Throwable throwable, Method method, Object... obj) {
System.out.println("Exception message - " + throwable.getMessage());
System.out.println("Method name - " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value - " + param);
}
}
}
В предыдущем разделе мы рассмотрели интерфейс AsyncConfigurer
, реализованный классом конфигурации. В рамках этого нам также необходимо переопределить метод getAsyncUncaughtExceptionHandler()
, чтобы вернуть наш собственный обработчик асинхронных исключений:
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
6. Заключение
В этой статье мы рассмотрели запуск асинхронного кода с помощью Spring.
Мы начали с самой базовой конфигурации и аннотации, чтобы заставить ее работать. Но мы также рассмотрели более продвинутые конфигурации, такие как предоставление собственного исполнителя или стратегии обработки исключений.
Как всегда, полный код, представленный в этой статье, доступен на GitHub .