Loading...
Spring Framework Reference Documentation 7.0.2의 Task Execution and Scheduling의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
The Spring Framework는 각각 TaskExecutor 및 TaskScheduler 인터페이스를 사용하여
task의 비동기 실행 및 스케줄링에 대한 추상화를 제공합니다. Spring은 또한
애플리케이션 서버 환경 내에서 스레드 풀을 지원하거나 CommonJ로 위임하는
이러한 인터페이스의 구현체를 제공합니다. 궁극적으로, 공통 인터페이스 뒤에서 이러한 구현을 사용하면
Java SE 및 Jakarta EE 환경 간의 차이를 추상화합니다.
Spring은 또한 Quartz Scheduler와의 스케줄링을 지원하기 위한 통합 클래스를 제공합니다.
TaskExecutor AbstractionExecutor는 스레드 풀 개념에 대한 JDK 이름입니다. “executor”라는 이름은 실제 구현이 반드시 풀이라는 보장이 없기 때문에 붙여졌습니다. Executor는 single-threaded일 수도 있고 심지어 동기식일 수도 있습니다. Spring의 추상화는 Java SE 및 Jakarta EE 환경 간의 구현 세부 사항을 숨깁니다.
Spring의 TaskExecutor 인터페이스는 java.util.concurrent.Executor
인터페이스와 동일합니다. 사실, 원래는 스레드 풀을 사용할 때 Java 5의 필요성을
추상화하기 위한 것이 주된 존재 이유였습니다. 이 인터페이스는 단일 메서드
(execute(Runnable task))를 가지며, 스레드 풀의 시맨틱 및 설정에 따라
실행할 task를 전달받습니다.
TaskExecutor는 원래 필요한 곳에서 다른 Spring 컴포넌트에 스레드 풀링에 대한 추상화를
제공하기 위해 만들어졌습니다. ApplicationEventMulticaster,
JMS의 AbstractMessageListenerContainer, Quartz 통합과 같은 컴포넌트는 모두
스레드를 풀하기 위해 TaskExecutor 추상화를 사용합니다. 그러나 빈이 스레드 풀링
동작을 필요로 한다면, 이 추상화를 자신의 요구에 맞게 사용할 수도 있습니다.
TaskExecutor TypesSpring에는 여러 개의 미리 만들어진 TaskExecutor 구현체가 포함되어 있습니다.
대부분의 경우, 직접 구현체를 만들 필요는 없습니다.
Spring이 제공하는 변형은 다음과 같습니다:
SyncTaskExecutor:
이 구현체는 호출을 비동기식으로 실행하지 않습니다. 대신 각
호출은 호출 스레드 내에서 수행됩니다. 주로 멀티스레딩이 필요하지 않은
단순 테스트 케이스와 같은 상황에서 사용됩니다.
SimpleAsyncTaskExecutor:
이 구현체는 어떤 스레드도 재사용하지 않습니다. 대신 각 호출마다 새로운 스레드를
시작합니다. 그러나, limit을 초과하는 호출을 slot이 비워질 때까지 block하는
동시성 limit을 지원합니다. 실제 풀링을 원한다면, 이 목록의 뒤에 나오는
ThreadPoolTaskExecutor를 참고하십시오.
"virtualThreads" 옵션이 활성화되면 JDK 21의 Virtual Threads를 사용합니다.
이 구현체는 또한 Spring의 라이프사이클 관리을 통해 graceful shutdown을 지원합니다.
ConcurrentTaskExecutor:
이 구현체는 java.util.concurrent.Executor 인스턴스에 대한 어댑터입니다.
빈 프로퍼티로 Executor 설정 파라미터를 노출하는 대안(ThreadPoolTaskExecutor)이 있습니다.
직접 ConcurrentTaskExecutor를 사용할 필요는 거의 없습니다. 그러나
ThreadPoolTaskExecutor가 요구 사항에 충분히 유연하지 않다면,
ConcurrentTaskExecutor가 대안이 됩니다.
ThreadPoolTaskExecutor:
이 구현체는 가장 일반적으로 사용됩니다. java.util.concurrent.ThreadPoolExecutor를
설정하기 위한 빈 프로퍼티를 노출하고 이를 TaskExecutor로 감쌉니다.
다른 종류의 java.util.concurrent.Executor에 적응해야 한다면,
대신 ConcurrentTaskExecutor를 사용할 것을 권장합니다.
또한 pause/resume 기능과 Spring의 라이프사이클 관리을 통한 graceful shutdown을 제공합니다.
DefaultManagedTaskExecutor:
이 구현체는 JSR-236 호환 런타임 환경(Jakarta EE 애플리케이션 서버 등)에서
JNDI로 얻은 ManagedExecutorService를 사용하며,
그 목적을 위해 CommonJ WorkManager를 대체합니다.
TaskExecutorSpring의 TaskExecutor 구현체는 일반적으로 의존성 주입과 함께 사용됩니다.
다음 예제에서는 ThreadPoolTaskExecutor를 사용하는 빈을 정의하여
메시지 집합을 비동기식으로 출력합니다:
1public class TaskExecutorExample { 2 3 private class MessagePrinterTask implements Runnable { 4 5 private String message; 6 7 public MessagePrinterTask(String message) { 8 this.message = message; 9 } 10 11 public void run() { 12 System.out.println(message); 13 } 14 } 15 16 private TaskExecutor taskExecutor; 17 18 public TaskExecutorExample(TaskExecutor taskExecutor) { 19 this.taskExecutor = taskExecutor; 20 } 21 22 public void printMessages() { 23 for(int i = 0; i < 25; i++) { 24 taskExecutor.execute(new MessagePrinterTask("Message" + i)); 25 } 26 } 27}
1class TaskExecutorExample(private val taskExecutor: TaskExecutor) { 2 3 private inner class MessagePrinterTask(private val message: String) : Runnable { 4 override fun run() { 5 println(message) 6 } 7 } 8 9 fun printMessages() { 10 for (i in 0..24) { 11 taskExecutor.execute( 12 MessagePrinterTask( 13 "Message$i" 14 ) 15 ) 16 } 17 } 18}
보는 바와 같이, 풀에서 스레드를 가져와 직접 실행하는 대신
Runnable을 큐에 추가합니다. 그러면 TaskExecutor가 내부 규칙을 사용하여
task가 언제 실행될지 결정합니다.
TaskExecutor가 사용하는 규칙을 설정하기 위해, 간단한 빈 프로퍼티를 노출합니다:
1@Bean 2ThreadPoolTaskExecutor taskExecutor() { 3 ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 4 taskExecutor.setCorePoolSize(5); 5 taskExecutor.setMaxPoolSize(10); 6 taskExecutor.setQueueCapacity(25); 7 return taskExecutor; 8} 9 10@Bean 11TaskExecutorExample taskExecutorExample(ThreadPoolTaskExecutor taskExecutor) { 12 return new TaskExecutorExample(taskExecutor); 13}
1@Bean 2fun taskExecutor() = ThreadPoolTaskExecutor().apply { 3 corePoolSize = 5 4 maxPoolSize = 10 5 queueCapacity = 25 6} 7 8@Bean 9fun taskExecutorExample(taskExecutor: ThreadPoolTaskExecutor) = TaskExecutorExample(taskExecutor)
1<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 2 <property name="corePoolSize" value="5"/> 3 <property name="maxPoolSize" value="10"/> 4 <property name="queueCapacity" value="25"/> 5</bean> 6 7<bean id="taskExecutorExample" class="TaskExecutorExample"> 8 <constructor-arg ref="taskExecutor"/> 9</bean>
대부분의 TaskExecutor 구현체는 제출된 task를 TaskDecorator로 자동 wrapping하는 방법을 제공합니다.
Decorator는 wrapping하고 있는 task에 delegate해야 하며, task 실행 전/후에
커스텀 동작을 구현할 수 있습니다.
이제 task 실행 전후에 메시지를 로그하는 단순 구현을 살펴보겠습니다:
1import org.apache.commons.logging.Log; 2import org.apache.commons.logging.LogFactory; 3 4import org.springframework.core.task.TaskDecorator; 5 6public class LoggingTaskDecorator implements TaskDecorator { 7 8 private static final Log logger = LogFactory.getLog(LoggingTaskDecorator.class); 9 10 @Override 11 public Runnable decorate(Runnable runnable) { 12 return () -> { 13 logger.debug("Before execution of " + runnable); 14 runnable.run(); 15 logger.debug("After execution of " + runnable); 16 }; 17 } 18}
1import org.apache.commons.logging.Log 2import org.apache.commons.logging.LogFactory 3import org.springframework.core.task.TaskDecorator 4 5class LoggingTaskDecorator : TaskDecorator { 6 7 override fun decorate(runnable: Runnable): Runnable { 8 return Runnable { 9 logger.debug("Before execution of $runnable") 10 runnable.run() 11 logger.debug("After execution of $runnable") 12 } 13 } 14 15 companion object { 16 private val logger: Log = LogFactory.getLog( 17 LoggingTaskDecorator::class.java 18 ) 19 } 20}
그런 다음 TaskExecutor 인스턴스에 decorator를 설정할 수 있습니다:
1@Bean 2ThreadPoolTaskExecutor decoratedTaskExecutor() { 3 ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 4 taskExecutor.setTaskDecorator(new LoggingTaskDecorator()); 5 return taskExecutor; 6}
1@Bean 2fun decoratedTaskExecutor() = ThreadPoolTaskExecutor().apply { 3 setTaskDecorator(LoggingTaskDecorator()) 4}
1<bean id="decoratedTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 2 <property name="taskDecorator" ref="loggingTaskDecorator"/> 3</bean>
여러 decorator가 필요한 경우,
org.springframework.core.task.support.CompositeTaskDecorator를 사용하여
여러 decorator를 순차적으로 실행할 수 있습니다.
TaskScheduler AbstractionTaskExecutor 추상화 외에도, Spring은 다양한 메서드로
미래의 특정 시점에 실행할 task를 스케줄링하기 위한 TaskScheduler SPI를 제공합니다.
다음 listing은 TaskScheduler 인터페이스 정의를 보여줍니다:
1public interface TaskScheduler { 2 3 Clock getClock(); 4 5 ScheduledFuture schedule(Runnable task, Trigger trigger); 6 7 ScheduledFuture schedule(Runnable task, Instant startTime); 8 9 ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period); 10 11 ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period); 12 13 ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay); 14 15 ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay); 16}
가장 단순한 메서드는 Runnable과 Instant만을 받는 schedule입니다.
이는 지정된 시간이 지난 후 task를 한 번 실행하게 합니다. 나머지 모든 메서드는
task를 반복적으로 실행하도록 스케줄링할 수 있습니다. fixed-rate 및 fixed-delay
메서드는 단순한 주기적 실행을 위한 것이지만, Trigger를 받는 메서드는
훨씬 더 유연합니다.
Trigger InterfaceTrigger 인터페이스는 본질적으로 JSR-236에서 영감을 받았습니다. Trigger의 기본 아이디어는
실행 시간이 과거 실행 결과 또는 임의의 조건에 기반하여 결정될 수 있다는 것입니다.
이러한 결정이 이전 실행의 결과를 고려하는 경우, 그 정보는 TriggerContext 내에서
사용할 수 있습니다. Trigger 인터페이스 자체는 다음 listing에서 볼 수 있듯이 매우 단순합니다:
1public interface Trigger { 2 3 Instant nextExecution(TriggerContext triggerContext); 4}
TriggerContext가 가장 중요합니다. 이는 모든 관련 데이터를 캡슐화하며,
필요할 경우 미래에 확장할 수 있도록 열려 있습니다.
TriggerContext는 인터페이스이며(기본적으로 SimpleTriggerContext 구현이 사용됩니다).
다음 listing은 Trigger 구현을 위한 사용 가능한 메서드를 보여줍니다.
1public interface TriggerContext { 2 3 Clock getClock(); 4 5 Instant lastScheduledExecution(); 6 7 Instant lastActualExecution(); 8 9 Instant lastCompletion(); 10}
Trigger ImplementationsSpring은 Trigger 인터페이스의 두 가지 구현체를 제공합니다. 그 중 가장 흥미로운 것은
CronTrigger입니다. 이는
cron expressions에
기반하여 task를 스케줄링할 수 있게 해줍니다.
예를 들어, 다음 task는 평일의 9시부터 5시까지의 "업무 시간" 동안에만
매 시 15분에 실행되도록 스케줄링됩니다:
1scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
다른 구현체는 fixed period, optional initial delay 값, 그리고 period를
fixed-rate로 해석할지 fixed-delay로 해석할지를 나타내는 boolean을 받는 PeriodicTrigger입니다.
TaskScheduler 인터페이스는 이미 fixed rate 또는 fixed delay로 task를 스케줄링하기 위한
메서드를 정의하고 있으므로, 가능한 경우 이러한 메서드를 직접 사용하는 것이 좋습니다.
PeriodicTrigger 구현의 가치는 Trigger 추상화에 의존하는 컴포넌트 내에서
이를 사용할 수 있다는 점입니다. 예를 들어, periodic trigger, cron-based trigger,
그리고 커스텀 trigger 구현을 서로 교환 가능하게 사용하는 것이 편리할 수 있습니다.
이러한 컴포넌트는 의존성 주입을 활용하여
외부에서 이러한 Trigger를 설정할 수 있으므로, 쉽게 수정하거나 확장할 수 있습니다.
TaskScheduler implementationsSpring의 TaskExecutor 추상화와 마찬가지로,
TaskScheduler 구성의 주요 이점은 애플리케이션의 스케줄링 요구 사항이
배포 환경과 분리된다는 점입니다.
이 추상화 레벨은 스레드를 애플리케이션 자체에서 직접 생성해서는 안 되는
애플리케이션 서버 환경에 배포할 때 특히 관련이 있습니다.
이러한 시나리오를 위해 Spring은 Jakarta EE 환경에서
JSR-236 ManagedScheduledExecutorService에 위임하는 DefaultManagedTaskScheduler를 제공합니다.
외부 스레드 관리가 요구 사항이 아닌 경우,
애플리케이션 내에서 로컬 ScheduledExecutorService를 설정하는 더 단순한 대안이 있으며,
이는 Spring의 ConcurrentTaskScheduler를 통해 어댑트될 수 있습니다.
편의를 위해 Spring은 또한 ThreadPoolTaskScheduler를 제공하며,
이는 내부적으로 ScheduledExecutorService에 위임하여
ThreadPoolTaskExecutor와 유사한 공통 빈 스타일 설정을 제공합니다.
이러한 변형은 관대한 애플리케이션 서버 환경(특히 Tomcat 및 Jetty)에서
로컬로 내장된 스레드 풀 설정에 대해서도 완벽하게 동작합니다.
6.1부터 ThreadPoolTaskScheduler는 pause/resume 기능과
Spring의 라이프사이클 관리을 통한 graceful shutdown을 제공합니다.
또한 SimpleAsyncTaskScheduler라는 새로운 옵션이 있으며,
이는 JDK 21의 Virtual Threads에 맞게 정렬되어,
단일 스케줄러 스레드를 사용하지만 각 scheduled task execution마다 새로운 스레드를 시작합니다
(fixed-delay task는 모두 단일 스케줄러 스레드에서 동작하므로,
이 virtual-thread-aligned 옵션에서는 fixed rate 및 cron trigger를 권장합니다).
Spring은 task 스케줄링 및 비동기 메서드 실행 모두에 대한 annotation 지원을 제공합니다.
@Scheduled 및 @Async annotation 지원을 활성화하려면,
다음 예제에서 보듯이 하나의 @Configuration 클래스에
@EnableScheduling 및 @EnableAsync를 추가하거나 <task:annotation-driven> 요소를
추가할 수 있습니다:
1@Configuration 2@EnableAsync 3@EnableScheduling 4public class SchedulingConfiguration { 5}
1@Configuration 2@EnableAsync 3@EnableScheduling 4class SchedulingConfiguration
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" 3 xsi:schemaLocation="http://www.springframework.org/schema/beans 4 https://www.springframework.org/schema/beans/spring-beans.xsd 5 http://www.springframework.org/schema/task 6 https://www.springframework.org/schema/task/spring-task.xsd"> 7 8 <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> 9 <task:executor id="myExecutor" pool-size="5"/> 10 <task:scheduler id="myScheduler" pool-size="10"/> 11</beans>
애플리케이션에 필요한 annotation만 선택적으로 사용할 수 있습니다.
예를 들어, @Scheduled에 대한 지원만 필요하다면 @EnableAsync를 생략할 수 있습니다.
더 세밀한 제어가 필요하다면, 추가로 SchedulingConfigurer
인터페이스, AsyncConfigurer 인터페이스 또는 둘 다를 구현할 수 있습니다.
자세한 내용은
SchedulingConfigurer
및 AsyncConfigurer
javadoc을 참고하십시오.
앞의 XML에서 보듯이, @Async annotation이 있는 메서드에 해당하는 task를 처리하기 위한
executor reference가 제공되고, @Scheduled가 annotation된 메서드를 관리하기 위한
scheduler reference가 제공된다는 점에 유의하십시오.
@Asyncannotation을 처리하기 위한 기본 advice mode는proxy이며,<br>이를 통해 proxy를 통한 호출만 interception할 수 있습니다. 동일한 클래스 내의 local 호출은<br>이 방식으로 interception될 수 없습니다. 더 고급 interception mode가 필요하다면,<br>compile-time 또는 load-time weaving과 결합하여aspectjmode로 전환하는 것을 고려하십시오.
@Scheduled annotation@Scheduled annotation을 메서드에 추가하고 trigger 메타데이터를 함께 지정할 수 있습니다.
예를 들어, 다음 메서드는 fixed delay로 5초(5000 밀리초)마다 호출됩니다.
이는 각 이전 호출의 완료 시간부터 period가 측정된다는 의미입니다.
1@Scheduled(fixedDelay = 5000) 2public void doSomething() { 3 // something that should run periodically 4}
기본적으로 fixed delay, fixed rate 및 initial delay 값에 대해 밀리초가 time unit으로 사용됩니다.<br>seconds나 minutes와 같은 다른 time unit을 사용하려면
@Scheduled의timeUnitattribute를 통해<br>이를 설정할 수 있습니다. 예를 들어, 이전 예제는 다음과 같이도 작성할 수 있습니다.<br><br>java<br>@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)<br>public void doSomething() {<br> // something that should run periodically<br>}<br>
fixed-rate execution이 필요하다면,
annotation 내에서 fixedRate attribute를 사용할 수 있습니다.
다음 메서드는 각 호출의 연속적인 시작 시간 사이를 기준으로
5초마다 호출됩니다:
1@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS) 2public void doSomething() { 3 // something that should run periodically 4}
fixed-delay 및 fixed-rate task의 경우,
다음 fixedRate 예제에서 보듯이 메서드의 첫 execution 이전에 대기할 시간인
initial delay를 지정할 수 있습니다:
1@Scheduled(initialDelay = 1000, fixedRate = 5000) 2public void doSomething() { 3 // something that should run periodically 4}
one-time task의 경우, 메서드의 의도된 execution 이전에 대기할 시간인 initial delay만 지정하면 됩니다:
1@Scheduled(initialDelay = 1000) 2public void doSomething() { 3 // something that should run only once 4}
단순 주기적 스케줄링만으로는 충분히 표현할 수 없는 경우, cron expression을 제공할 수 있습니다. 다음 예제는 평일에만 실행됩니다:
1@Scheduled(cron="*/5 * * * * MON-FRI") 2public void doSomething() { 3 // something that should run on weekdays only 4}
zoneattribute를 사용하여 cron expression이 해석되는 time zone을 지정할 수도 있습니다.
스케줄될 메서드는 void return type이어야 하며 argument를 받아서는 안 된다는 점에 유의하십시오. 메서드가 애플리케이션 컨텍스트의 다른 객체와 상호 작용해야 하는 경우, 이는 일반적으로 의존성 주입을 통해 제공됩니다.
@Scheduled는 repeatable annotation으로 사용할 수 있습니다.
동일한 메서드에서 여러 개의 scheduled 선언이 발견되면,
각각은 독립적으로 처리되며, 각 선언마다 별도의 trigger가 firing됩니다.
결과적으로, 이러한 co-located schedule은 서로 겹칠 수 있으며
병렬로 또는 즉시 연속적으로 여러 번 실행될 수 있습니다.
지정한 cron expression 등이 실수로 겹치지 않도록 주의하십시오.
Spring Framework 4.3부터
@Scheduled메서드는 모든 scope의 빈에서 지원됩니다.<br>runtime에 동일한@Scheduledannotation 클래스의 여러 인스턴스를 초기화하지 않도록<br>주의하십시오(각 인스턴스에 대해 callback을 스케줄링하려는 의도가 있는 경우는 예외).<br>이와 관련하여, container에 regular Spring 빈으로 등록된@Scheduled가 annotation된<br>빈 클래스에@Configurable을 사용하지 않도록 하십시오. 그렇지 않으면<br>container를 통한 한 번과@Configurableaspect를 통한 한 번, 총 두 번의 초기화가 발생하여,<br>각@Scheduled메서드가 두 번 호출되는 결과를 초래합니다.
@Scheduled annotation on Reactive methods or Kotlin suspending functionsSpring Framework 6.1부터 @Scheduled 메서드는 여러 유형의 reactive 메서드에서도
지원됩니다:
Publisher return type(또는 Publisher의 모든 구체적 구현)을 가지는 메서드:1@Scheduled(fixedDelay = 500) 2public Publisher<Void> reactiveSomething() { 3 // return an instance of Publisher 4}
ReactiveAdapterRegistry 인스턴스를 통해 Publisher로 어댑트될 수 있는
return type을 가지며, 해당 type이 _deferred subscription_을 지원하는 메서드:1@Scheduled(fixedDelay = 500) 2public Single<String> rxjavaNonPublisher() { 3 return Single.just("example"); 4}
CompletableFuture클래스는 일반적으로Publisher로 어댑트될 수 있지만<br>deferred subscription을 지원하지 않는 type의 예입니다. registry 내의 해당ReactiveAdapter는<br>getDescriptor().isDeferred()메서드가false를 반환하도록 하여 이를 나타냅니다.
1@Scheduled(fixedDelay = 500) 2suspend fun something() { 3 // do something asynchronous 4}
Flow 또는 Deferred 인스턴스를 return하는 메서드:1@Scheduled(fixedDelay = 500) 2fun something(): Flow<Void> { 3 flow { 4 // do something asynchronous 5 } 6}
이러한 모든 유형의 메서드는 argument 없이 선언되어야 합니다.
Kotlin suspending function의 경우,
framework가 suspending function을 Publisher로 호출할 수 있도록
kotlinx.coroutines.reactor bridge도 존재해야 합니다.
Spring Framework는 annotation된 메서드에 대해 Publisher를 한 번 얻고,
해당 Publisher에 subscribe하는 Runnable을 스케줄링합니다.
이 내부의 일반 subscription은 해당 cron/fixedDelay/fixedRate 설정에 따라 발생합니다.
Publisher가 onNext signal을 emit하는 경우, 이는 무시되고 폐기됩니다
(동기식 @Scheduled 메서드의 return value가 무시되는 것과 동일한 방식).
다음 예제에서 Flux는 5초마다 onNext("Hello"), onNext("World")를 emit하지만,
이 값은 사용되지 않습니다:
1@Scheduled(initialDelay = 5000, fixedRate = 5000) 2public Flux<String> reactiveSomething() { 3 return Flux.just("Hello", "World"); 4}
Publisher가 onError signal을 emit하는 경우, 이는 WARN level로 로그되고 복구됩니다.
Publisher 인스턴스의 비동기 및 lazy 특성 때문에,
exception은 Runnable task에서 throw되지 않습니다.
이는 reactive 메서드에 대해 ErrorHandler contract가 관여하지 않는다는 의미입니다.
결과적으로, error가 발생하더라도 이후의 scheduled subscription은 계속 발생합니다.
다음 예제에서, Mono subscription은 처음 5초 동안 두 번 실패합니다.
그 후 subscription이 성공하기 시작하여 5초마다 표준 출력에 메시지를 출력합니다:
1@Scheduled(initialDelay = 0, fixedRate = 5000) 2public Mono<Void> reactiveSomething() { 3 AtomicInteger countdown = new AtomicInteger(2); 4 5 return Mono.defer(() -> { 6 if (countDown.get() == 0 || countDown.decrementAndGet() == 0) { 7 return Mono.fromRunnable(() -> System.out.println("Message")); 8 } 9 return Mono.error(new IllegalStateException("Cannot deliver message")); 10 }); 11}
annotation된 빈을 destroy하거나 애플리케이션 컨텍스트를 close할 때,<br>Spring Framework는 scheduled task를 cancel합니다. 여기에는
Publisher에 대한 다음 scheduled<br>subscription뿐만 아니라 현재까지 active 상태인 과거 subscription(예: long-running publisher나<br>무한 publisher의 경우)도 포함됩니다.
@Async annotation@Async annotation을 메서드에 제공하면 해당 메서드 호출이 비동기식으로
발생하도록 할 수 있습니다. 다시 말해, caller는 호출 후 즉시 return하며,
실제 메서드 실행은 Spring TaskExecutor에 제출된 task 내에서 발생합니다.
가장 단순한 경우, 다음 예제와 같이 void를 return하는 메서드에 annotation을 적용할 수 있습니다:
1@Async 2void doSomething() { 3 // this will be run asynchronously 4}
@Scheduled annotation이 있는 메서드와 달리,
이러한 메서드는 runtime에 caller에 의해 “normal” 방식으로 호출되므로
argument를 받을 수 있습니다(즉, container에서 관리되는 scheduled task로부터
호출되는 것이 아닙니다). 예를 들어, 다음 코드는 @Async annotation의
적법한 적용 예입니다:
1@Async 2void doSomething(String s) { 3 // this will be run asynchronously 4}
값을 return하는 메서드도 비동기식으로 호출할 수 있습니다.
그러나 이러한 메서드는 Future type의 return value를 가져야 합니다.
이렇게 하면 caller가 Future에서 get()을 호출하기 전에 다른 작업을 수행할 수 있으므로
비동기 실행의 이점은 그대로 유지됩니다.
다음 예제는 값을 return하는 메서드에 @Async를 사용하는 방법을 보여줍니다:
1@Async 2Future<String> returnSomething(int i) { 3 // this will be run asynchronously 4}
@Async메서드는 regularjava.util.concurrent.Futurereturn type뿐만 아니라<br>Spring의org.springframework.util.concurrent.ListenableFuture또는 Spring 4.2부터는<br>JDK 8의java.util.concurrent.CompletableFuture도 선언할 수 있습니다. 이를 통해<br>비동기 task와의 richer한 상호 작용 및 추가 처리 단계와의 즉각적인 composition이 가능해집니다.
@Async를 @PostConstruct와 같은 라이프사이클 callback과 함께 사용할 수는 없습니다.
Spring 빈을 비동기식으로 초기화하려면,
현재로서는 별도의 initializing Spring 빈을 사용하여 target의
@Async annotation이 붙은 메서드를 호출해야 합니다. 다음 예제를 참고하십시오:
1public class SampleBeanImpl implements SampleBean { 2 3 @Async 4 void doSomething() { 5 // ... 6 } 7 8} 9 10public class SampleBeanInitializer { 11 12 private final SampleBean bean; 13 14 public SampleBeanInitializer(SampleBean bean) { 15 this.bean = bean; 16 } 17 18 @PostConstruct 19 public void initialize() { 20 bean.doSomething(); 21 } 22 23}
@Async에 대한 직접적인 XML equivalent는 없습니다. 이러한 메서드는 처음부터<br>비동기 실행을 위해 설계되어야 하며, 외부에서 비동기식으로 재선언되어서는<br>안 되기 때문입니다. 그러나 Spring AOP와 함께 Spring의AsyncExecutionInterceptor를<br>수동으로 설정하고 커스텀 pointcut과 결합할 수는 있습니다.
@Async기본적으로 메서드에 @Async를 지정하면,
async support를 활성화할 때 설정된
executor가 사용됩니다. 즉, XML을 사용하는 경우 “annotation-driven” 요소나,
존재하는 경우 AsyncConfigurer 구현이 사용됩니다.
그러나 특정 메서드를 실행할 때 기본 executor가 아닌 다른 executor를 사용해야 하는 경우,
@Async annotation의 value attribute를 사용할 수 있습니다.
다음 예제는 이를 보여줍니다:
1@Async("otherExecutor") 2void doSomething(String s) { 3 // this will be run asynchronously by "otherExecutor" 4}
이 경우 "otherExecutor"는 Spring container 내의 어떤 Executor 빈의 이름이 될 수도 있고,
<qualifier> 요소나 Spring의 @Qualifier annotation으로 지정된
어떤 Executor에 연관된 qualifier 이름일 수도 있습니다.
@Async@Async 메서드가 Future type의 return value를 가지는 경우,
메서드 실행 중에 throw된 exception은 Future result에서 get을 호출할 때
throw되므로 쉽게 관리할 수 있습니다. 그러나 void return type의 경우,
exception은 catch되지 않으며 전달될 수 없습니다.
이러한 exception을 처리하기 위해 AsyncUncaughtExceptionHandler를 제공할 수 있습니다.
다음 예제는 이를 보여줍니다:
1public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler { 2 3 @Override 4 public void handleUncaughtException(Throwable ex, Method method, Object... params) { 5 // handle exception 6 } 7}
기본적으로 exception은 단순히 로그됩니다.
AsyncConfigurer나 <task:annotation-driven/> XML 요소를 사용하여
커스텀 AsyncUncaughtExceptionHandler를 정의할 수 있습니다.
task Namespace3.0 버전부터 Spring은 TaskExecutor 및 TaskScheduler 인스턴스를 설정하기 위한
XML namespace를 포함합니다. 또한 trigger로 스케줄링될 task를 설정하는
편리한 방법도 제공합니다.
scheduler Element다음 요소는 지정된 스레드 풀 size를 가진 ThreadPoolTaskScheduler 인스턴스를 생성합니다:
1<task:scheduler id="scheduler" pool-size="10"/>
id attribute에 제공된 값은 풀 내의 스레드 이름에 대한 prefix로 사용됩니다.
scheduler 요소는 비교적 간단합니다. pool-size attribute를 제공하지 않으면
기본 스레드 풀은 single thread만 가집니다.
scheduler에 대한 다른 설정 옵션은 없습니다.
executor Element다음은 ThreadPoolTaskExecutor 인스턴스를 생성합니다:
1<task:executor id="executor" pool-size="10"/>
이전 section에서
보여준 scheduler와 마찬가지로,
id attribute에 제공된 값은 풀 내의 스레드 이름에 대한 prefix로 사용됩니다.
풀 size와 관련하여, executor 요소는 scheduler 요소보다 더 많은
설정 옵션을 지원합니다. 한 가지 이유는 ThreadPoolTaskExecutor의
스레드 풀 자체가 더 설정 가능하기 때문입니다. 단일 size만 있는 것이 아니라,
executor의 스레드 풀은 core size와 max size에 대해 서로 다른 값을 가질 수 있습니다.
단일 값을 제공하면 executor는 fixed-size 스레드 풀(core와 max size가 동일)을 가집니다.
그러나 executor 요소의 pool-size attribute는 min-max 형식의 range도 허용합니다.
다음 예제는 minimum 값 5와 maximum 값 25를 설정합니다:
1<task:executor 2 id="executorWithPoolSizeRange" 3 pool-size="5-25" 4 queue-capacity="100"/>
위 설정에서는 queue-capacity 값도 제공되었습니다.
스레드 풀의 설정은 executor의 큐 capacity 관점에서도 고려해야 합니다.
풀 size와 큐 capacity 간의 관계에 대한 전체 설명은
ThreadPoolExecutor
documentation을 참고하십시오.
핵심 아이디어는 task가 제출될 때, active 스레드 수가 현재 core size보다 적으면
executor가 먼저 free 스레드를 사용하려 한다는 것입니다.
core size에 도달한 경우, 큐의 capacity가 아직 도달하지 않았다면
task는 큐에 추가됩니다. 그 후에야 큐 capacity가 도달했을 때
executor는 core size를 초과하는 새로운 스레드를 생성합니다.
max size도 도달한 경우, executor는 task를 reject합니다.
기본적으로 큐는 unbounded이지만, 이는 대부분 원하는 설정이 아닙니다.
왜냐하면 모든 풀 스레드가 busy인 동안 큐에 충분한 task가 추가되면
OutOfMemoryError로 이어질 수 있기 때문입니다.
또한 큐가 unbounded인 경우 max size는 전혀 효과가 없습니다.
executor는 core size를 초과하는 새 스레드를 생성하기 전에 항상 큐를 먼저 시도하므로,
스레드 풀이 core size를 넘어 성장하려면 큐에 finite capacity가 있어야 합니다
(unbounded 큐를 사용할 때 fixed-size 풀만이 유일하게 합리적인 경우인 이유입니다).
앞에서 언급한 것처럼, task가 reject되는 경우를 고려해 보십시오.
기본적으로 task가 reject되면 스레드 풀 executor는 TaskRejectedException을 throw합니다.
그러나 rejection policy는 실제로 설정 가능합니다.
기본 rejection policy인 AbortPolicy 구현을 사용할 때 exception이 throw됩니다.
heavy load에서 일부 task를 건너뛸 수 있는 애플리케이션의 경우,
대신 DiscardPolicy 또는 DiscardOldestPolicy를 설정할 수 있습니다.
heavy load에서 제출된 task를 throttling해야 하는 애플리케이션에 잘 맞는 또 다른 옵션은
CallerRunsPolicy입니다. 이 policy는 exception을 throw하거나 task를 discard하는 대신,
submit 메서드를 호출하는 스레드가 직접 task를 실행하도록 강제합니다.
이 아이디어는 그러한 caller가 해당 task를 실행하는 동안 busy 상태가 되어
즉시 다른 task를 제출할 수 없게 된다는 것입니다.
따라서 스레드 풀 및 큐의 limit을 유지하면서 들어오는 load를 throttling하는
단순한 방법을 제공합니다. 일반적으로 이는 executor가 처리 중인 task를 “따라잡을” 수 있게 하여,
큐나 풀 또는 둘 다에서 어느 정도 capacity를 확보할 수 있게 합니다.
executor 요소의 rejection-policy attribute에 사용 가능한 enumeration 값 중에서
이러한 옵션 중 하나를 선택할 수 있습니다.
다음 예제는 다양한 동작을 지정하기 위한 여러 attribute를 가진 executor 요소를 보여줍니다:
1<task:executor 2 id="executorWithCallerRunsPolicy" 3 pool-size="5-25" 4 queue-capacity="100" 5 rejection-policy="CALLER_RUNS"/>
마지막으로, keep-alive 설정은 스레드가 중지되기 전에 idle 상태로 남을 수 있는
time limit(초)을 결정합니다.
풀 내의 스레드 수가 core number보다 많은 경우,
이 시간 동안 task를 처리하지 않고 기다리면 초과 스레드는 중지됩니다.
time 값이 0이면, task를 실행한 후 task 큐에 follow-up work가 남아 있지 않은 경우
초과 스레드는 즉시 중지됩니다.
다음 예제는 keep-alive 값을 2분으로 설정합니다:
1<task:executor 2 id="executorWithKeepAlive" 3 pool-size="5-25" 4 keep-alive="120"/>
scheduled-tasks ElementSpring의 task namespace에서 가장 강력한 기능은
Spring 애플리케이션 컨텍스트 내에서 스케줄링될 task를 설정하는 지원입니다.
이는 JMS namespace가 message-driven POJO를 설정하기 위해 제공하는 것과 같은
다른 Spring의 “method-invoker” 접근 방식과 유사합니다.
기본적으로 ref attribute는 Spring에서 관리되는 어떤 객체라도 가리킬 수 있으며,
method attribute는 해당 객체에서 호출할 메서드의 이름을 제공합니다.
다음 listing은 단순 예제를 보여줍니다:
1<task:scheduled-tasks scheduler="myScheduler"> 2 <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/> 3</task:scheduled-tasks> 4 5<task:scheduler id="myScheduler" pool-size="10"/>
scheduler는 outer 요소에 의해 reference되며,
각 개별 task는 trigger 메타데이터의 설정을 포함합니다.
위 예제에서 해당 메타데이터는 각 task execution이 완료된 후 기다릴 밀리초 수를 나타내는
fixed delay를 가진 periodic trigger를 정의합니다.
다른 옵션으로는 이전 execution이 얼마나 오래 걸렸는지와 관계없이
메서드가 얼마나 자주 실행되어야 하는지를 나타내는 fixed-rate가 있습니다.
또한 fixed-delay 및 fixed-rate task 모두에 대해,
메서드의 첫 execution 이전에 기다릴 밀리초 수를 나타내는
initial-delay 파라미터를 지정할 수 있습니다.
더 많은 제어가 필요하면, 대신 cron attribute를 제공하여
cron expression을
제공할 수 있습니다. 다음 예제는 이러한 다른 옵션을 보여줍니다:
1<task:scheduled-tasks scheduler="myScheduler"> 2 <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/> 3 <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/> 4 <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/> 5</task:scheduled-tasks> 6 7<task:scheduler id="myScheduler" pool-size="10"/>
Spring cron expression은
@Scheduled annotations,
task:scheduled-tasks elements,
또는 다른 어떤 곳에서 사용하든 모두 동일한 format을 따라야 합니다.
* * * * * *와 같은 well-formed cron expression은
각각 고유한 유효 값 range를 가진 여섯 개의 space-separated time 및 date field로 구성됩니다:
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *
몇 가지 규칙이 적용됩니다:
field는 항상 “first-last”를 의미하는 asterisk(*)가 될 수 있습니다.
day-of-the-month 또는 day-of-the-week field의 경우,
asterisk 대신 question mark(?)를 사용할 수 있습니다.
comma(,)는 list의 item을 구분하는 데 사용됩니다.
hyphen(-)으로 구분된 두 숫자는 숫자 range를 표현합니다.
지정된 range는 inclusive입니다.
range(또는 *) 뒤에 /를 붙이면 range 전체에서 숫자 값의 interval을 지정합니다.
month 및 day-of-week field에는 영어 이름도 사용할 수 있습니다. 해당 day 또는 month의 첫 세 글자를 사용하십시오(대소문자는 상관없습니다).
day-of-month 및 day-of-week field에는 L 문자가 포함될 수 있으며,
이는 서로 다른 의미를 가집니다.
day-of-month field에서 L은 _해당 month의 마지막 날_을 의미합니다.
negative offset(즉, L-n)이 뒤따르면 _month의 마지막에서 n번째 날_을 의미합니다.
day-of-week field에서 L은 _week의 마지막 날_을 의미합니다.
숫자 또는 세 글자 이름(dL 또는 DDDL)이 prefix로 붙으면
_month에서 week의 마지막 날 (d 또는 DDD)_을 의미합니다.
day-of-month field는 nW가 될 수 있으며,
이는 _day of the month n에 가장 가까운 weekday_를 의미합니다.
n이 Saturday에 해당하면, 이는 그 전 Friday를 나타냅니다.
n이 Sunday에 해당하면, 이는 그 다음 Monday를 나타내며,
이는 n이 1이고 Saturday에 해당하는 경우에도 마찬가지입니다
(즉, 1W는 _month의 첫 weekday_를 의미합니다).
day-of-month field가 LW이면, 이는 _month의 마지막 weekday_를 의미합니다.
day-of-week field는 d#n(또는 DDD#n)이 될 수 있으며,
이는 _month에서 n번째 day of week d(또는 DDD)_를 의미합니다.
다음은 몇 가지 예입니다:
| Cron Expression | Meaning |
|---|---|
0 0 * * * * | 매일 매 시 정각 |
*/10 * * * * * | 10초마다 |
0 0 8-10 * * * | 매일 8, 9, 10시 정각 |
0 0 6,19 * * * | 매일 오전 6시와 오후 7시 |
0 0/30 8-10 * * * | 매일 8:00, 8:30, 9:00, 9:30, 10:00, 10:30 |
0 0 9-17 * * MON-FRI | 평일 9시부터 5시까지 매 시 정각 |
0 0 0 25 DEC ? | 매년 Christmas Day 자정 |
0 0 0 L * * | 매달 마지막 날 자정 |
0 0 0 L-3 * * | 매달 마지막에서 세 번째 날 자정 |
0 0 0 * * 5L | 매달 마지막 Friday 자정 |
0 0 0 * * THUL | 매달 마지막 Thursday 자정 |
0 0 0 1W * * | 매달 첫 weekday 자정 |
0 0 0 LW * * | 매달 마지막 weekday 자정 |
0 0 0 ? * 5#2 | 매달 두 번째 Friday 자정 |
0 0 0 ? * MON#1 | 매달 첫 Monday 자정 |
0 0 * * * *와 같은 expression은 사람이 parse하기 어렵기 때문에
bug가 있을 경우 수정하기도 어렵습니다. 가독성을 높이기 위해, Spring은 자주 사용되는
sequence를 나타내는 다음 macro를 지원합니다. 이러한 macro를 six-digit 값 대신
사용할 수 있습니다. 예: @Scheduled(cron = "@hourly").
| Macro | Meaning |
|---|---|
@yearly (or @annually) | 1년에 한 번 (0 0 0 1 1 *) |
@monthly | 한 달에 한 번 (0 0 0 1 * *) |
@weekly | 일주일에 한 번 (0 0 0 * * 0) |
@daily (or @midnight) | 하루에 한 번 (0 0 0 * * *), 또는 |
@hourly | 한 시간에 한 번 (0 0 * * * *) |
Quartz는 모든 종류의 job 스케줄링을 실현하기 위해
Trigger, Job, JobDetail 객체를 사용합니다.
Quartz의 기본 개념에 대해서는
Quartz Web site를 참고하십시오.
편의상, Spring은 Spring 기반 애플리케이션 내에서 Quartz 사용을 단순화하는
몇 가지 클래스를 제공합니다.
JobDetailFactoryBeanQuartz JobDetail 객체는 job을 실행하는 데 필요한 모든 정보를 포함합니다.
Spring은 XML 설정을 위한 빈 스타일 프로퍼티를 제공하는
JobDetailFactoryBean을 제공합니다.
다음 예제를 참고하십시오:
1<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> 2 <property name="jobClass" value="example.ExampleJob"/> 3 <property name="jobDataAsMap"> 4 <map> 5 <entry key="timeout" value="5"/> 6 </map> 7 </property> 8</bean>
job detail 설정은 job(ExampleJob)을 실행하는 데 필요한 모든 정보를 가지고 있습니다.
timeout은 job data map에 지정됩니다. job data map은
(execution 시점에 전달되는) JobExecutionContext를 통해 사용할 수 있지만,
JobDetail은 또한 job data를 job 인스턴스의 프로퍼티에 매핑하여 프로퍼티를 얻습니다.
따라서 다음 예제에서 ExampleJob은 timeout이라는 빈 프로퍼티를 포함하며,
JobDetail은 이를 자동으로 적용합니다:
1package example; 2 3public class ExampleJob extends QuartzJobBean { 4 5 private int timeout; 6 7 /** 8 * Setter called after the ExampleJob is instantiated 9 * with the value from the JobDetailFactoryBean. 10 */ 11 public void setTimeout(int timeout) { 12 this.timeout = timeout; 13 } 14 15 protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { 16 // do the actual work 17 } 18}
job data map의 모든 추가 프로퍼티도 사용할 수 있습니다.
name및group프로퍼티를 사용하여 각각 job의 이름과 group을 수정할 수 있습니다.<br>기본적으로 job의 이름은JobDetailFactoryBean의 빈 이름과 일치합니다(위 예제의exampleJob).
MethodInvokingJobDetailFactoryBean종종 특정 객체의 메서드를 호출하기만 하면 되는 경우가 있습니다.
MethodInvokingJobDetailFactoryBean을 사용하면 다음 예제와 같이
정확히 그렇게 할 수 있습니다:
1<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 2 <property name="targetObject" ref="exampleBusinessObject"/> 3 <property name="targetMethod" value="doIt"/> 4</bean>
위 예제는 다음 예제에서 보듯이
exampleBusinessObject의 doIt 메서드가 호출되도록 합니다:
1public class ExampleBusinessObject { 2 3 // properties and collaborators 4 5 public void doIt() { 6 // do the actual work 7 } 8}
1<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
MethodInvokingJobDetailFactoryBean을 사용하면
단순히 메서드를 호출하기만 하는 one-line job을 만들 필요가 없습니다.
실제 비즈니스 객체만 만들고 detail 객체를 wiring하면 됩니다.
기본적으로 Quartz Job은 stateless이므로,
job이 서로 간섭할 가능성이 있습니다.
동일한 JobDetail에 두 개의 trigger를 지정하면,
첫 번째 job이 끝나기 전에 두 번째 job이 시작될 수 있습니다.
JobDetail 클래스가 Stateful 인터페이스를 구현하면,
이러한 일이 발생하지 않습니다. 첫 번째 job이 끝나기 전에는
두 번째 job이 시작되지 않습니다.
MethodInvokingJobDetailFactoryBean에서 생성된 job을 non-concurrent로 만들려면,
다음 예제와 같이 concurrent flag를 false로 설정하십시오:
1<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 2 <property name="targetObject" ref="exampleBusinessObject"/> 3 <property name="targetMethod" value="doIt"/> 4 <property name="concurrent" value="false"/> 5</bean>
기본적으로 job은 concurrent 방식으로 실행됩니다.
SchedulerFactoryBean우리는 job detail과 job을 생성했습니다.
또한 특정 객체의 메서드를 호출할 수 있는 convenience 빈도 살펴보았습니다.
물론, 여전히 job 자체를 스케줄링해야 합니다.
이는 trigger와 SchedulerFactoryBean을 사용하여 수행합니다.
Quartz 내에는 여러 trigger가 있으며,
Spring은 편리한 default를 가진 두 개의 Quartz FactoryBean 구현을 제공합니다:
CronTriggerFactoryBean 및 SimpleTriggerFactoryBean.
Trigger는 스케줄링되어야 합니다.
Spring은 trigger를 프로퍼티로 설정할 수 있도록 하는 SchedulerFactoryBean을 제공합니다.
SchedulerFactoryBean은 실제 job을 해당 trigger로 스케줄링합니다.
다음 listing은 SimpleTriggerFactoryBean과 CronTriggerFactoryBean을 모두 사용합니다:
1<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> 2 <!-- see the example of method invoking job above --> 3 <property name="jobDetail" ref="jobDetail"/> 4 <!-- 10 seconds --> 5 <property name="startDelay" value="10000"/> 6 <!-- repeat every 50 seconds --> 7 <property name="repeatInterval" value="50000"/> 8</bean> 9 10<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> 11 <property name="jobDetail" ref="exampleJob"/> 12 <!-- run every morning at 6 AM --> 13 <property name="cronExpression" value="0 0 6 * * ?"/> 14</bean>
위 예제는 두 개의 trigger를 설정합니다.
하나는 10초의 starting delay 후 50초마다 실행되고,
다른 하나는 매일 오전 6시에 실행됩니다.
모든 것을 마무리하려면, 다음 예제와 같이 SchedulerFactoryBean을 설정해야 합니다:
1<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> 2 <property name="triggers"> 3 <list> 4 <ref bean="cronTrigger"/> 5 <ref bean="simpleTrigger"/> 6 </list> 7 </property> 8</bean>
SchedulerFactoryBean에는 job detail에서 사용하는 calendar,
Quartz를 커스터마이징하기 위한 프로퍼티, Spring이 제공하는 JDBC DataSource 등
더 많은 프로퍼티가 있습니다.
자세한 내용은
SchedulerFactoryBean
javadoc을 참고하십시오.
SchedulerFactoryBean은 regular Quartz 설정과 마찬가지로,<br>Quartz 프로퍼티 key를 기반으로 classpath 내의quartz.propertiesfile도 인식합니다.<br>많은SchedulerFactoryBean설정이 properties file 내의 공통 Quartz 설정과<br>상호 작용하므로, 두 수준 모두에서 값을 지정하는 것은 권장되지 않습니다.<br>예를 들어 Spring이 제공하는 DataSource에 의존하려는 경우에는<br>"org.quartz.jobStore.class" 프로퍼티를 설정하지 말아야 합니다.<br>또는 표준org.quartz.impl.jdbcjobstore.JobStoreTX를 완전히 대체하는<br>org.springframework.scheduling.quartz.LocalDataSourceJobStore변형을 지정해야 합니다.
Cache Abstraction