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.

Режим снабжения 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<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();
    }
}
Прямого XML-эквивалента для аннотации @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/>.