Spring обеспечивает поддержку аннотаций как для планирования задач, так и для асинхронного выполнения методов.
Активация аннотации планирования
Чтобы активировать поддержку аннотаций @Scheduled
и @Async
, можно добавить аннотации @EnableScheduling
и @EnableAsync
в один из ваших классов, помеченных аннотацией @Configuration
, как показано в следующем примере:
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
Вы можете выбирать аннотации, подходящие для вашего приложения. Например, если вам требуется лишь поддержка аннотации @Scheduled
, аннотацию @EnableAsync
можно опустить. Для более тонкого контроля можно дополнительно реализовать интерфейс SchedulingConfigurer
, интерфейс AsyncConfigurer
или оба интерфейса одновременно. Подробную информацию смотрите в javadoc по SchedulingConfigurer
и AsyncConfigurer
.
Если вы предпочитаете конфигурацию XML, то можете использовать элемент <task:annotation-driven>
, как показано в следующем примере:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
Обратите внимание, что в предыдущем XML ссылка на исполнитель указана для обработки тех задач, которые соответствуют методам с аннотацией @Async
, а ссылка на планировщик указана для управления методами, аннотированными @Scheduled
.
@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<String> 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();
}
}
@Async
не существует, поскольку такие методы должны изначально предназначаться для асинхронного выполнения, а не объявляться асинхронными извне. Однако, можно вручную настроить AsyncExecutionInterceptor
из Spring с помощью Spring AOP в сочетании с пользовательским срезом.Уточнение исполнителя с помощью аннотации @Async
По умолчанию при задании аннотации @Async
для метода используется исполнитель, сконфигурированный при активации средств поддержки асинхронной обработки, т.е. "управляемый аннотациями" компонент, если вы используете XML, или ваша реализация AsyncConfigurer
, если таковая имеется. Однако можно использовать атрибут value
аннотации @Async
, если указать, что при выполнении данного метода должен использоваться исполнитель, отличный от используемого по умолчанию. В следующем примере показано, как это сделать:
@Async("otherExecutor")
void doSomething(String s) {
// будет выполняться асинхронно через "otherExecutor"
}
В этом случае "otherExecutor"
может быть именем любого бина Executor
в контейнере Spring, или же это может быть имя квалификатора, связанного с любым Executor
(например, заданного с помощью элемента <qualifier>
или аннотации @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-элемента <task:annotation-driven/>
.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ