Поддержка аннотаций для планирования и асинхронного выполнения
Spring обеспечивает поддержку аннотаций как для планирования задач, так и для асинхронного выполнения методов.
Активация аннотации планирования
Чтобы активировать поддержку аннотаций @Scheduled и @Async , можно добавить аннотации @EnableScheduling и @EnableAsync в один из ваших классов, помеченных аннотацией @Configuration , как показано в следующем примере:
@Configuration @EnableAsync @EnableScheduling public class AppConfig
Вы можете выбирать аннотации, подходящие для вашего приложения. Например, если вам требуется лишь поддержка аннотации @Scheduled , аннотацию @EnableAsync можно опустить. Для более тонкого контроля можно дополнительно реализовать интерфейс SchedulingConfigurer , интерфейс AsyncConfigurer или оба интерфейса одновременно. Подробную информацию смотрите в javadoc по SchedulingConfigurer и AsyncConfigurer .
Если вы предпочитаете конфигурацию XML, то можете использовать элемент , как показано в следующем примере:
task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> task:executor id="myExecutor" pool-size="5"/> task:scheduler id="myScheduler" pool-size="10"/>
Обратите внимание, что в предыдущем XML ссылка на исполнитель указана для обработки тех задач, которые соответствуют методам с аннотацией @Async , а ссылка на планировщик указана для управления методами, аннотированными @Scheduled .
Режим снабжения Advice-ами по умолчанию для обработки аннотаций @Transactional – это proxy , что позволяет перехватывать вызовы только через прокси. Локальные вызовы в пределах одного класса перехватить таким же образом нельзя. Для более расширенного режима перехвата рассмотрите возможность перехода на режим aspectj в сочетании со связыванием во время компиляции или во время загрузки.
Аннотация @Scheduled
Вы можете добавить аннотацию @Scheduled к методу вместе с метаданными триггера. Например, следующий метод вызывается каждые пять секунд (5000 миллисекунд) с фиксированной задержкой, то есть период измеряется от времени завершения каждого предыдущего вызова.
@Scheduled(fixedDelay = 5000) public void doSomething() < // нечто, что должно выполняться периодически >
По умолчанию в качестве единицы времени для значений фиксированной задержки, фиксированной частоты и начальной задержки будут использоваться миллисекунды. Если необходимо использовать другую единицу времени, например, секунды или минуты, то можно настроить это с помощью атрибута timeUnit в аннотации @Scheduled .
Например, предыдущий пример можно записать следующим образом.
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() < // нечто, что должно выполняться периодически >
Если необходимо осуществлять выполнение с фиксированной частотой, то можно использовать атрибут fixedRate в аннотации. Следующий метод вызывается каждые пять секунд (измеряется между последовательными отрезками времени начала каждого вызова).
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() < // нечто, что должно выполняться периодически >
Для задач с фиксированной задержкой и фиксированной частотой можно задать начальную задержку, указав количество времени ожидания перед первым выполнением метода, как показано в следующем примере fixedRate .
@Scheduled(initialDelay = 1000, fixedRate = 5000) public void doSomething() < // нечто, что должно выполняться периодически >
Если простое периодическое планирование не является достаточно выразительным, можно использовать выражение в формате cron. Следующий пример работает только для будних дней:
@Scheduled(cron="*/5 * * * * MON-FRI") public void doSomething() < // что-то, что нужно выполнять только в будние дни >
Также можно использовать атрибут zone , чтобы задать часовой пояс, в котором разрешается выражение в формате cron.
Обратите внимание, что планируемые методы должны возвращать значение void и не должны принимать никаких аргументов. Если методу необходимо взаимодействовать с другими объектами из контекста приложения, они обычно передаются через внедрение зависимостей.
Начиная с версии Spring Framework 4.3, методы, помеченные аннотацией @Scheduled , поддерживаются для бинов любой области доступности.
Убедитесь, что не инициализируете несколько экземпляров одного и того же класса аннотации @Scheduled в среде выполнения, если не хотите планировать обратные вызовы для каждого такого экземпляра. В частности, убедитесь, что не используете аннотацию @Configurable для классов бинов, которые зарегистрированы в контейнере как обычные бины Spring. В противном случае вы получите двойную инициализацию (один раз через контейнер и один раз через аспект с аннотацией @Configurable ), что приведет к тому, что каждый метод, аннотированный @Scheduled , будет вызываться дважды.
Аннотация @Async
Вы можете указать аннотацию @Async для метода, чтобы вызов этого метода происходил асинхронно. Иными словами, вызывающий код возвращается сразу после вызова, в то время как фактическое выполнение метода происходит в задаче, которая была передана TaskExecutor для Spring. В простейшем случае можно применить аннотацию к методу, который возвращает void , как показано в следующем примере:
@Async void doSomething() < // будет выполняться асинхронно >
В отличие от методов, помеченных аннотацией @Scheduled , эти методы могут принимать аргументы, поскольку вызываются «обычным» способом вызывающими программами во время выполнения, а не из запланированной задачи, управляемой контейнером. Например, следующий код является допустимым применением аннотации @Async :
@Async void doSomething(String s) < // будет выполняться асинхронно >
Даже те методы, которые возвращают какое-то значение, могут быть вызваны асинхронно. Однако такие методы должны иметь возвращаемое значение типа Future . Это по-прежнему позволит пользоваться преимуществами асинхронного выполнения, поэтому вызывающий код сможет выполнять другие задачи до вызова get() для этого Future . В следующем примере показано, как использовать аннотацию @Async в методе, возвращающем какое-то значение:
@Async Future returnSomething(int i) < // будет выполняться асинхронно >
Методы с аннотацией @Async могут объявлять не только обычный возвращаемый тип java.util.concurrent.Future , но и возвращаемый тип org.springframework.util.concurrent.ListenableFuture для Spring или, начиная с Spring 4.2, тип java.util.concurrent.CompletableFuture для JDK 8, чтобы обеспечить более полнофункциональное взаимодействие с асинхронной задачей и немедленное компонование с дальнейшими этапами обработки.
Нельзя использовать @Async в сочетании с обратными вызовами жизненного цикла, такими как @PostConstruct . Для асинхронной инициализации бинов Spring в настоящее время необходимо использовать отдельный инициализирующий бин Spring, который затем вызывает помеченный аннотацией @Async метод для цели, как показано в следующем примере:
public class SampleBeanImpl implements SampleBean < @Async void doSomething() < // . > > public class SampleBeanInitializer < private final SampleBean bean; public SampleBeanInitializer(SampleBean bean) < this.bean = bean; > @PostConstruct public void initialize() < bean.doSomething(); >>
Прямого XML-эквивалента для аннотации @Async не существует, поскольку такие методы должны изначально предназначаться для асинхронного выполнения, а не объявляться асинхронными извне. Однако, можно вручную настроить AsyncExecutionInterceptor из Spring с помощью Spring AOP в сочетании с пользовательским срезом.
Уточнение исполнителя с помощью аннотации @Async
По умолчанию при задании аннотации @Async для метода используется исполнитель, сконфигурированный при активации средств поддержки асинхронной обработки, т.е. «управляемый аннотациями» компонент, если вы используете XML, или ваша реализация AsyncConfigurer , если таковая имеется. Однако можно использовать атрибут value аннотации @Async , если указать, что при выполнении данного метода должен использоваться исполнитель, отличный от используемого по умолчанию. В следующем примере показано, как это сделать:
@Async("otherExecutor") void doSomething(String s) < // будет выполняться асинхронно через "otherExecutor" >
В этом случае «otherExecutor» может быть именем любого бина Executor в контейнере Spring, или же это может быть имя квалификатора, связанного с любым Executor (например, заданного с помощью элемента или аннотации @Qualifier из Spring).
Управление исключениями с помощью аннотации @Async
Если метод с аннотацией @Async имеет возвращаемое значение типа Future , то нетрудно управлять исключением, которое было сгенерировано во время выполнения метода, поскольку это исключение будет генерироваться при вызове get для результирующего Future . Однако при возвращаемом типе void исключение не перехватывается и не может быть передано. Можно передать AsyncUncaughtExceptionHandler для обработки таких исключений. В следующем примере показано, как это сделать:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler < @Override public void handleUncaughtException(Throwable ex, Method method, Object. params) < // обрабатываем исключение > >
По умолчанию исключение лишь регистрируется. Можно определить кастомный AsyncUncaughtExceptionHandler с помощью AsyncConfigurer или XML-элемента .