Перейти к основному содержимому

Как сделать @Async весной

· 5 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим поддержку асинхронного выполнения в Spring и аннотацию @Async .

Проще говоря, аннотирование метода компонента с помощью @Async заставит его выполняться в отдельном потоке. Другими словами, вызывающая сторона не будет ждать завершения вызываемого метода.

Одним из интересных аспектов Spring является то, что поддержка событий в фреймворке также поддерживает асинхронную обработку , если это необходимо.

2. Включите асинхронную поддержку ``

Начнем с включения асинхронной обработки с конфигурацией Java.

Мы сделаем это, добавив @EnableAsync в класс конфигурации:

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

Достаточно включить аннотацию. Но есть и несколько простых вариантов настройки:

  • аннотация — по умолчанию @EnableAsync обнаруживает аннотацию Spring @Async и EJB 3.1 javax.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 .