Spring은 스케쥴링을 지원하는 통합 클래스들을 제공한다. 현재적으로, Spring은 1.3 이후버전 JDK의 일부분인 Timer와 Quartz 스케쥴러 (http://www.opensymphony.com/quartz/)를 지원하고 있다. 이 두개의 스케쥴러들은 각각 Timer 혹은 Trigger에 대한 선택적 참조를 가지는 FactoryBean을 사용하여 세팅된다. 게다가 당신이 타겟 object의 메써드를 편리하게 호출할 수 있도록 도와주는 Quartz 스케쥴러와 Timer에 대한 편의 클래스를 제공한다.(이것은 일반적인 MethodInvokingFactoryBeans와 비슷하다.) Spring은 Java 1.3, 1.4, 5와 J2EE환경간의 차이점을 추상화하는 쓰레드 풀링을 위한 클래스를 제공한다.
Quartz는 Trigger, Job 그리고 모든 종류의 jobs를 인식하고 있는 JobDetail를 사용한다. Quartz에 깔려 있는 기본적인 개념을 알고 싶다면, http://www.opensymphony.com/quartz를 찾아보길 바란다. 편리한 사용을 위해서, Spring은 Spring 기반 어플리케이션 내에서 Quartz의 사용을 손쉽게 만들어주는 두 개의 클래스들을 제공한다.
JobDetail 객체는 job을 실행하기 위해 필요한 모든 정보를 가지고 있다. Spring은 소위 JobDetailBean이라고 불리는 클래스를 제공하는데, 이것은 JobDetail을 합리적인 디폴트값을 가진 실질적인 JavaBean 객체로 만들어준다. 다음의 예제를 보도록 하자.
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="example.ExampleJob" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="5" /> </map> </property> </bean>
위의 job detail bean은 job(ExampleJob)을 실행하기 위한 모든 정보를 가지고 있다. 타임아웃은 job data map으로 기술되었다. job data map은 (실행시 넘겨지는) JobExecutionContext를 통해 이용할 수 있지만, JobDetailBean 역시 job data map으로부터 실질적인 job의 프라퍼티들을 매핑할 수 있다. 때문에 이러한 경우, 만약 ExampleJob이 timeout이라는 프라퍼티를 가지고 있다면, JobDetailBean은 그것을 자동으로 적용할 것이다. v
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
당신은 job detail bean의 모든 부가적인 세팅들 역시 마찬가지로 이용할 수 있다.
주의: name과 group 프라퍼티를 사용함으로써, 당신은 job의 name과 group을 변경할 수 있다. default로 job의 이름은 job detail bean의 이름과 동일하다.(위의 예에서는 exampleJob이 된다.)
종종 당신은 특정한 객체의 메써드를 호출할 필요가 있을 것이다. 당신은 MethodInvokingJobDetailFactoryBean을 사용하여 다음과 같이 할 수 있다.
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="exampleBusinessObject" /> <property name="targetMethod" value="doIt" /> </bean>
위의 예는 (아래에 있는) exampleBusinessObject메소드에서 호출되는 doIt메소드의 결과일것이다.
public class ExampleBusinessObject { // properties and collaborators public void doIt() { // do the actual work } }
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
MethodInvokingJobDetailFactoryBean을 사용할 때, 메소드를 호출할 한줄짜리 jobs를 생성할 필요가 없으며, 당신은 단지 실질적인 비지니스 객체를 생성해서 그것을 묶기만 하면된다.
default로는 Quartz Jobs는 비상태이며, 상호 작용하는 jobs의 가능성을 가진다. 만약 당신이 동일한 JobDetail에 대해 두 개의 triggers를 명시한다면, 첫번째 job이 끝나기 이전에 두번째가 시작할지도 모른다. 만약 JobDetail 객체가 Stateful 인터페이스를 구현한다면, 이런 일은 발생하지 않을 것이다. 두번째 job은 첫번째가 끝나기 전에는 시작하지 않을 것이다. MethodInvokingJobDetailFactoryBean를 사용한 jobs가 동시작용하지 않도록 만들기 위해서는, concurrent 플래그를 false로 세팅해주어야 한다.
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="exampleBusinessObject" /> <property name="targetMethod" value="doIt" /> <property name="concurrent" value="false" /> </bean>
![]() | Note |
---|---|
기본적으로 jobs는 concurrent 옵션에 따라 실행될 것이다. |
우리는 job details과 jobs를 생성했고, 당신이 특정 객체의 메써드를 호출할 수 있도록 하는 편의클래스 bean을 살펴보았다. 물론, 우리는 여전히 jobs를 그자체로 스케쥴할 필요가 있다. 이것은 triggers와 SchedulerFactoryBean을 사용하여 이루어진다. 여러가지 triggers는 Quartz 내에서 이용할 수 있다. Spring은 편의를 위해 2개의 상속받은 triggers를 기본적으로 제공한다. :CronTriggerBean과 SimpleTriggerBean이 그것이다.
Triggers는 스케쥴될 필요가 있다. Spring은 triggers를 세팅하기 위한 프라퍼티들을 드러내는 SchedulerFactoryBean을 제공하고 있다. SchedulerFactoryBean은 그 triggers와 함께 실질적인 jobs를 스케쥴한다.
다음 두가지 예를 보자.
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <!-- see the example of method invoking job above --> <property name="jobDetail" ref="jobDetail" /> <!-- 10 seconds --> <property name="startDelay" value="10000" /> <!-- repeat every 50 seconds --> <property name="repeatInterval" value="50000" /> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="exampleJob" /> <!-- run every morning at 6 AM --> <property name="cronExpression" value="0 0 6 * * ?" /> </bean>
OK, 이제 우리는 두 개의 triggers를 세팅했다. 하나는 10초 늦게 실행해서 매 50초마다 실행될 것이고, 다른 하나는 매일 아침 6시에 실행될 것이다. 모든 것을 완료하기 위해서, 우리는 SchedulerFactoryBean을 세팅해야 한다.
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> <ref bean="simpleTrigger" /> </list> </property> </bean>
당신이 세팅할 수 있는 더욱 많은 프라퍼티들이 SchedulerFactoryBean에 있다. 이를테면, job details에 의해 사용되는 calendars라던가, Quartz를 커스터마이징할 수 있게 하는 프라퍼티같은 것들이 말이다. 더 많은 정보를 위해서는 JavaDoc(SchedulerFactoryBean Javadoc)을 참조하도록 해라.
Spring에서 스케쥴링 업무를 처리하는 또다른 방법은 JDK Timer 객체들을 사용하는 것이다. Timer 자체에 대한 더 많은 정보는 http://java.sun.com/docs/books/tutorial/essential/threads/timer.html에서 찾아볼 수 있다. 위에서 살펴 본 기본개념들은 Timer support에도 마찬가지로 적용된다. 당신은 임의의 timers를 생성하고 메써드들을 호출하기 위해 timer를 사용한다. TimerFactoryBean을 사용하여 timers를 묶는다.
당신은 TimerTask를 사용하여 임의의 timer tasks를 생성할 수 있다. 이것은 Quartz jobs와 유사하다.
public class CheckEmailAddresses extends TimerTask {
private List emailAddresses;
public void setEmailAddresses(List emailAddresses) {
this.emailAddresses = emailAddresses;
}
public void run() {
// iterate over all email addresses and archive them
}
}
이것을 묶는 것 역시 간단하다:
<bean id="checkEmail" class="examples.CheckEmailAddress"> <property name="emailAddresses"> <list> <value>test@springframework.org</value> <value>foo@bar.com</value> <value>john@doe.net</value> </list> </property> </bean> <bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask"> <!-- wait 10 seconds before starting repeated execution --> <property name="delay" value="10000" /> <!-- run every 50 seconds --> <property name="period" value="50000" /> <property name="timerTask" ref="checkEmail" /> </bean>
task를 단지 한번만 실행하고자 한다면, period 속성을 0(혹은 음수값)으로 바꿔주면 된다.
Quartz support와 비슷하게, Timer 역시 당신이 주기적으로 메써드를 호출할 수 있도록 하는 요소들을 기술한다.
<bean id="doIt" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean"> <property name="targetObject" ref="exampleBusinessObject" /> <property name="targetMethod" value="doIt" /> </bean>
위 결과는 exampleBusinessObject 에서 호출되는 doIt 메소드의 결과가 될것이다.
public class BusinessObject { // properties and collaborators public void doIt() { // do the actual work } }
ScheduledTimerTask 예제의 timerTask 참조를 bean doIt으로 변경하는 것은 고정된 스케줄에서 수행되는 doIt메소드의 결과가 될것이다.
TimerFactoryBean은 실질적인 스케쥴링을 세팅한다는 같은 목적을 제공한다는 점에서 Quartz의 SchedulerFactoryBean과 비슷하다. TimerFactoryBean는 실질적인 Timer를 세팅하고 그것이 참조하고 있는 tasks를 스케쥴한다. 당신은 대몬 쓰레드를 사용할 것인지 말것인지를 기술할 수 있다.
<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<!-- see the example above -->
<ref bean="scheduledTask" />
</list>
</property>
</bean>
Spring 2.0은 실행자(Executor)를 다루기 위해 새로운 추상화를 소개한다. 실행자(Executor)는 쓰레드 풀링의 개념을 위한 Java 5의 이름이다. 뜻밖의(odd) 명명은 참조하는 구현물이 실제로 풀(pool)인것을 보증하지 않는 사실에 기반한다. 사실, 많은 경우, 실행자(executor)는 쓰레드 하나로 이루어진다. Spring의 추상화는 1.3, 1.4, 5와 Java EE환경간의 상세한 구현을 감추는 만큼 Java 1.3, 1.4환경에 쓰레드 풀링을 적용하도록 도와준다.
Spring의 TaskExecutor 인터페이스는 java.util.concurrent.Executor에 일치한다. 사실, 이것이 존재하는 가장 큰 이유는 쓰레드 풀을 사용할때 Java 5에 필요한 추상화이다. 인터페이스는 의미론적인 수행과 쓰레드 풀의 설정을 위한 작업을 받는 하나의 메소드인 execute(Runnable task)를 가진다.
TaskExecutor는 필요한 쓰레드 풀링을 위한 추상화에 다른 Spring컴포넌트를 주기 위해 생성되었다. ApplicationEventMulticaster와 같은 컴포넌트인 JMS의 AbstractMessageListenerContainer와 Quartz통합은 모두 풀 쓰레드를 위해 TaskExecutor 추상화를 사용한다. 어쨌뜬, bean이 쓰레드 풀링이 필요하다면, 자체적인 필요를 위해 이 추상화를 사용하는 것이 가능하다.
Spring배포판에 포함된 TaskExecutor의 미리 빌드된 많은 구현물이 있다. 모든 가능성에서, 자체적으로 구현할 필요는 없을것이다.
SimpleAsyncTaskExecutor
이 구현물은 어떤 쓰레드도 재사용하지 않는다. 각각의 호출에 새로운 쓰레드를 시작한다. 어쨌뜬, 슬롯이 자유로워질때까지 제한을 넘어선 호출은 블럭할 동시 제한을 지원한다. 진정한 풀링을 찾는다면, 페이지 아래로 스크롤하라.
이 구현물은 호출을 비동기적으로 수행하지 않는다. 대신, 각각의 호출은 호출 쓰레드로 대체된다. 간단한 테스트케이스와 같이 필요하지 않은 멀티쓰레드 상황에서 사용된다.
이 구현물은 Java 5 java.util.concurrent.Executor를 위한 래퍼(wrapper)이다. 대안으로 bean프라퍼티로 Executor 설정 파라미터를 나타내는 ThreadPoolTaskExecutor가 있다. ConcurrentTaskExecutor를 사용할 필요는 드물지만 ThreadPoolTaskExecutor가 필요한 것만큼 충분히 견고하지 않다면, ConcurrentTaskExecutor이 대안이 된다.
이 구현물은 실제로 Spring의 생명주기 콜백을 듣는 Quartz의 SimpleThreadPool의 하위클래스이다. 이것은 Quartz와 Quartz가 아닌 컴포넌트간에 공유될필요가 있는 쓰레드 풀을 가질때 사용된다.
이 구현물은 Java 5 환경에서 사용될수 있지만 그 환경에서 가장 공통적으로 사용되는 것이다. java.util.concurrent.ThreadPoolExecutor를 설정하고 TaskExecutor에 그것을 포장하기 위한 bean프라퍼티를 나타낸다. ScheduledThreadPoolExecutor처럼 향상된 어떤것이 필요하다면, 대신 ConcurrentTaskExecutor를 사용하도록 추천한다.
TimerTaskExecutor
이 구현물은 지원 구현물로 하나의 TimerTask를 사용한다. 이것은 비록 쓰레드에서 동기적이더라도 메소드 호출이 개별 쓰레드에서 수행되어 SyncTaskExecutor와 다르다.
WorkManagerTaskExecutor
이 구현물은 지원 구현물로 CommonJ WorkManager을 사용하고 Spring 컨텍스트에서 CommonJ WorkManager참조를 셋팅하기 위한 중심적이고 편리한 클래스이다. SimpleThreadPoolTaskExecutor와 유사하게, 이 클래스는 WorkManager인터페이스를 구현하고 WorkManager만큼 직접 사용될수 있다.
Spring의 TaskExecutor 구현물은 간단한 JavaBean처럼 사용된다. 아래의 예제에서, 우리는 메시지의 세트를 비동기적으로 프린트하기 위한 ThreadPoolTaskExecutor을 사용하는 bean을 정의한다.
import org.springframework.core.task.TaskExecutor; public class TaskExecutorExample { private class MessagePrinterTask implements Runnable { private String message; public MessagePrinterTask(String message) { this.message = message; } public void run() { System.out.println(message); } } private TaskExecutor taskExecutor; public TaskExecutorExample(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } public void printMessages() { for(int i = 0; i < 25; i++) { taskExecutor.execute(new MessagePrinterTask("Message" + i)); } } }
당신이 볼수 있는것처럼, 풀에서 쓰레드를 가져오고 스스로 수행하는 것보다, 큐에 Runnable를 추가하고 TaskExecutor는 작업이 수행될때 결정할 내부 규칙을 사용한다.
TaskExecutor가 사용할 규칙을 설정하기 위해, 간단한 bean프라퍼티로 나타낸다.
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="5" /> <property name="maxPoolSize" value="10" /> <property name="queueCapacity" value="25" /> </bean> <bean id="taskExecutorExample" class="TaskExecutorExample"> <constructor-arg ref="taskExecutor" /> </bean>