Scheduling Service

Spring Framework는 Scheduling 서비스를 제공하기 위해서 다음과 같이, JDK 1.3이후 제공되는 Timer Scheduler와 Quartz Scheduler( http://www.opensymphony.com/quartz/ )를 제공한다.

Quartz는 오픈 소스 작업 스케줄링 프레임워크이다. Quartz는 완전히 자바로 작성되어 있으며 매우 유연하고 단순한 구조를 제공하여 간단한 작업은 물론 복잡한 작업 모두에 대한 스케줄링을 작성할 수 있다. 또한 EJB, JavaMail 등을 위한 데이터베이스 지원, 클러스터링, 플러그 인, 미리 내장된 작업들을 포함하고 있다.

Anyframe Core에서는 JDK의 Timer Scheduler보다 Quartz Scheduler 사용을 추천한다. Quartz Scheduler는 JDK Timer Scheduler보다 더 유동성있고 성능이 좋다. 자세한 내용은 Spring 매뉴얼의 Scheduling 부분 을 참고하도록 한다.

Quartz Scheduler

Quartz는 Job과 Trigger 그리고 JobDetail을 사용하여 스케줄링 기능을 수행한다. Quartz에 대한 자세한 내용은 http://www.opensymphony.com/quartz 를 참고한다.
  • Job은 실행해야 할 작업으로, 자동 문자메시지 전송 기능이나 어플리케이션의 자동화된 작업들이 그 예이다. Job은 언제 실행되는지에 대한 정보 없이 실행 작업의 단위로만 작성된다.
    Job은 Spring Framework에서 제공하는 QuartzJobBean을 상속받아서 작성할 수도 있고, 특정 API에 종속되지 않은 POJO 형태의 자바 클래스로 작성할 수 있다. 또한 Stateful한 Job 클래스를 사용하기 위해서 Quartz에서 제공하는 StatefulJob 인터페이스를 구현하여 Job을 작성할 수도 있다. Stateful 인터페이스를 구현하여 작성된 Job 클래스 이외의 모든 Job 클래스는 기본적으로 Stateless한 Job 클래스로 동작한다. Stateful한 Job의 경우에만 반복되는 Job 수행 시 특정 데이터 값을 공유하여 변경할 수 있다.
  • Trigger는 Job을 실행시키기 위한 조건으로 작업 실행 시간, 반복 횟수 그리고 실행 간격 시간 등이 조건에 해당된다. 다수의 Trigger는 동일한 Job을 공유하여 지정할 수 있으나, 하나의 Trigger는 반드시 하나의 Job을 지정해야 한다.
  • JobDetail은 Job을 실행하기 위해 필요한 정보를 가지고 있는, 즉 Job Instance에 대한 상세 속성 정보를 가지고 있는 객체로 Trigger가 JobDetail을 이용하여 Job을 수행시킴으로써 Quartz 기반의 스케줄링 기능이 수행된다.
  • Scheduler는 SchedulerFactory에 의해 생성되는데, JobDetail들과 Trigger들을 관리하며 해당 Trigger에 연관된 Job을 수행시키는 Scheduling 서비스의 핵심 역할을 담당한다.

아래 예제에서는 JobDetailBean, MethodInvokingJobDetailFactoryBean, SimpleTriggerBean, CronTriggerBean, TriggerListener, SchedulerFactoryBean 등을 이용하여 일정한 시간 간격 마다 특정 메소드를 실행시키는 방법을 보여주고 있다.
  • JobDetailBean : Spring Framework은 JobDetailBean이라고 불리는 클래스를 제공하는데, Job을 실행시키기 위해 필요한 정보를 가지고 있다.
  • MethodInvokingJobDetailFactoryBean : Job API에 종속적이지 않게 POJO 형태의 Job 클래스를 작성하여 Scheduler에 따라 해당 클래스의 메소드를 호출할 수 있도록 해주는 역할을 수행한다.
    Job과 Trigger에 관련된 정보를 DB를 통해 관리하는 경우, 즉 JDBC Job Store 방식을 사용하는 경우에는 MethodInvokingJobDetailFactoryBean이 동작하지 않으므로 사용할 수 없음에 유의하도록 한다.
  • SimpleTriggerBean, CronTriggerBean : Spring Framework에서 Quartz를 손쉽게 사용할 수 있도록 2개의 TriggerBean을 제공한다. SimpleTriggerBean과 CronTriggerBean은 Trigger로써 동작하는 것은 동일하나 CronTriggerBean의 경우, 시간 실행 조건을 cron expression을 이용하여 작성하는 것이 차이점이다. SimpleTrigger보다 상세한 스케줄링 실행 시간 조건을 설정할 수 있으면서도 설정 방법이 복잡하지 않다.
  • TriggerListenerBean : Trigger가 fire->execute->complete 혹은 misfire되는 시점에 특정 일을 수행하고자 할때 Scheduler에 TriggerListener를 설정한다.
  • SchedulerFactoryBean : 사용되는 Trigger를 등록시켜주는 역할을 담당하므로 Trigger들을 triggers 속성의 <list> 태그 하위로 등록시킨다. Trigger에 적용할 Listener 등 여러 속성들을 추가 설정할 수 있다.
위에 나열한 Bean들의 주요 속성 정의 항목부터 살펴보도록 하자.
다음은 JobDetailBean의 주요 속성으로 정의되어야 할 항목들에 대한 설명이다.
Property Name
Description
Required
Default Value
jobClass Job의 클래스명을 설정한다. Y N/A
jobDataAsMap 이 속성값으로 설정된 데이터들은 Job 클래스 내에서 사용가능한 데이터로 Map 형태로 정의한다. Job과 연관된 Trigger에 설정된 jobDataAsMap 속성값으로 설정한 Map 데이터와 JobDetail에 설정된 jobDataAsMap 속성값으로 설정한 Map 데이터는 함께 merge되어 사용될 수 있다.
이 데이터들은 기본적으로 처음 한번 설정된 뒤 변경되지 않으나, Stateful한 Job 객체를 실행시켜서 데이터 값을 변경시키게 되는 경우에는 변경이 가능하다.
N N/A

다음은 MethodInvokingJobDetailFactoryBean의 주요 속성으로 정의되어야 할 항목들에 대한 설명이다.
Property Name
Description
Required
Default Value
name Job의 이름을 설정한다. N FactoryBean의 Bean name
targetObj POJO로 작성된 Job 클래스명을 정의한다. Y N/A
targetMethod targetObj에서 설정한 Object에서 실행하고자 하는 method 명을 입력한다. Y N/A
concurrent 다수의 Job을 동시에 실행시키려면 true로 설정하고 순차적으로 실행시키려면 false로 설정한다. N true

다음은 SimpleTriggerBean의 주요 속성으로 정의되어야 할 항목들에 대한 설명이다.
Property Name
Description
Required
Default Value
jobDetail 이 Trigger와 관련된 JobDetail의 클래스를 작성하는데, Spring Bean으로 작성된 경우 ref attribute를 이용하여 Bean의 id를 작성하도록 한다. Y N/A
repeatInterval Trigger가 처음 수행 이후 반복 수행 시킬 Trigger의 interval 시간을 설정하는 값으로 Trigger가 반복되는 interval 시간을 milliseconds 단위로 설정한다. repeatInterval은 0이상이어야 한다. N 0
repeatCount Trigger가 처음 수행 이후 반복 수행 시킬 Trigger의 수행 횟수를 설정한다. N 0
triggerListenerNames 특정 trigger에만(즉, Non-global) TriggerListener를 설정하여 Trigger가 fire->execute->complete 혹은 misfire되는 시점에 특정 일을 수행하고자 할때 TriggerListener를 설정하는데 여기에는 triggerListenerName을 등록한다. 이 triggerListenerName은 TriggerListener 인터페이스를 구현한 클래스의 getName() 메소드에서 리턴한 이름을 작성하도록 한다. N N/A
jobDataAsMap 이 속성값으로 설정된 데이터들은 Trigger에 설정된 JobDetail을 통해 Job에서 해당 데이터를 이용할 수 있다. Trigger에 설정된 jobDataAsMap 속성값으로 설정한 Map 데이터와 JobDetail에 설정된 jobDataAsMap 속성값으로 설정한 Map 데이터는 함께 merge되어 사용될 수 있다.
이외에도 이 Trigger에 설정된 triggerListener 내부에서도 이 데이터를 이용할 수 있다.
이 데이터들은 기본적으로 처음 한번 설정된 뒤 변경되지 않으나, Stateful한 Job 객체를 실행시켜서 데이터 값을 변경시키게 되는 경우에는 변경이 가능하다.
N N/A
startTime Trigger 수행 시작 시간을 설정한다. N N/A
startDelay 처음 Job이 시작되기 전 지연 시간을 설정한다. 입력한 milliseconds 단위의 시간에 현재 시간을 더하면 시작 시간이 된다. startDelay 속성은 startTime 속성이 지정되지 않은 경우에 적용된다. 그러나 Spring Container를 구동시키면, startTime은 언제나 Container가 구동된 시간이므로 이러한 경우에는 절대적인 시간이 아닌 상대적인 시간을 설정하도록 한다. N 0
endTime Trigger 반복을 종료할 시간을 입력한다. N N/A
misfireInstructionName Trigger는 Scheduler에 문제가 발생하였거나 Job을 수행시키는 Quartz의 Thread pool에서 현재 사용 가능한 Thread가 존재하지 않을 때 실행되지 못하는 경우가 있다. 이러한 경우 Job Store 방식으로 DB를 사용하고 있다면, 다음 Scheduler가 정상 동작 시 실행되지 못한 Job들에 대해서 misfire 시킨다. (하위 Advanced Quartz >> Job Stores 내용 참고)
기본적으로 'smart policy' instruction이 사용되고, 그외 다음과 같은 misfire instruction명을 설정할 수 있다. 자세한 내용은 Quartz Tutorial >> Misfire Instructions 를 참조하도록 한다.
  • MISFIRE_INSTRUCTION_FIRE_NOW
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_
    WITH_EXISTING_REPEAT_COUNT
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_
    WITH_REMAINING_REPEAT_COUNT
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_
    WITH_REMAINING_COUNT
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_
    WITH_EXISTING_COUNT
이 속성 설정 시, 아래 소개된 SchedulerFactoryBean 속성 중 dataSource 속성 값도 설정하도록 한다.
N MISFIRE_
INSTRUCTION_
SMART_
POLICY

다음은 CronTriggerBean의 주요 속성으로 정의되어야 할 항목들에 대한 설명이다.
Property Name
Description
Required
Default Value
jobDetail 이 Trigger와 관련된 JobDetail의 클래스를 작성하는데, Spring Bean으로 작성된 경우 ref attribute를 이용하여 Bean의 id를 작성하도록 한다. Y N/A
cronExpression Trigger가 반복되는 interval 시간을 milliseconds 단위로 설정한다. cron expression은 기본적으로 다음과 같이 7개 필드로 구성되어 있다.
  • Seconds
  • Minutes
  • Hours
  • Day-of-month
  • Month
  • Day-of-week
  • Year (optional field)
예를 들어, cron expression이 0 0 6 * * ? 으로 설정되었다면 매일 오전 6시에 실행시키라는 의미로 설정된 것이다.
N 0
startTime Trigger 수행 시작 시간을 설정한다. N N/A
endTime Trigger 반복을 종료할 시간을 입력한다. N N/A

다음은 SchedulerFactoryBean의 주요 속성으로 정의되어야 할 항목들에 대한 설명이다.
Property Name
Description
Required
Default Value
triggers Scheduler에 사용되는 Trigger들을 등록시킨다. 여러 개의 Trigger를 등록시켜야 하므로 <list> 태그를 이용하여 설정한다. Y N/A
triggerListeners 특정 Trigger에 한하여 적용할 TriggerListener를 등록한다. N N/A
globalTriggerListeners 해당 Scheduler에 등록된 모든 Trigger들에게 적용할 TriggerListener를 등록한다. 이를 GlobalTriggerListener라고 한다. N N/A
dataSource 이 속성 값을 설정하게 되면, Job과 Trigger에 관련된 정보가 자동으로 DB를 통해 관리된다. 이때 저장할 DB Table이 준비되어 있어야 하는데, Table은 Quartz를 다운로드 받은 후 docs/dbTables 폴더 내의 table-creation SQL script 중 사용하고자 하는 DB(ex. Oracle의 경우, tables_oracle.sql)에 맞는 script를 수행하여 생성시키도록 한다. N N/A

Advanced Quartz

이 매뉴얼에 소개된 내용을 통해서도 Quartz의 많은 기능을 사용할 수 있으나, 이외에도 여러 고급 기능 등이 존재한다. 여기서는 Job과 Trigger에 관련된 정보를 저장하는 방법에 대해서 소개한다. 더욱 자세한 내용은 http://www.opensymphony.com/quartz 를 참고한다.
  • Job Stores
  • Quartz는 Job과 Trigger에 관련된 정보를 2가지 서로 다른 방식으로 저장할 수 있게 한다. 기본적으로 선택적으로 DB에 저장할 수도 있다. 기본(Default)적으로 RAMJobStore 클래스를 이용하여 Memory에 저장되는데, 이 Job Store 방식을 사용할 때 모든 데이터가 Memory에 저장되기 때문에 성능이 가장 좋다. 그러나, 어플리케이션이나 시스템에 문제가 발생하면 RAM에 저장되어있던 모든 데이터가 손실될 위험성이 있다.

    위의 Memory 저장 방식의 문제점을 해결하기 위해 Quartz는 JDBCJobStore 클래스를 이용하여 데이터를 DB에 저장하도록 한다. 모든 데이터가 JDBC를 통해서 DB에 저장되므로 어플리케이션이나 시스템에 문제가 발생해도 데이터가 손실될 위험은 없으나 성능이 좋지 않고 복잡성이 증가한다.
    데이터를 DB에 저장하는 방식을 사용하기 위해서는 위의 SchedulerFactoryBean 속성 중 dataSource 속성 값을 설정해줘야 하는데 dataSource 속성 설정 방법은 본 매뉴얼 >> Tech. Service >> DataSource 를 참고한다.

    Job과 Trigger에 관련된 정보를 DB를 통해 관리하기 위해서는 DB Table이 준비되어 있어야 하는데, Table은 Quartz를 다운로드 받은 후 docs/dbTables 폴더 내의 table-creation SQL script 중 사용하고자 하는 DB(ex. Oracle의 경우, tables_oracle.sql)에 맞는 script를 수행하여 생성시키도록 한다. 더욱 자세한 내용과 사용 방법은 Quartz Configure DataSources 를 참고하도록 한다.

Samples

다음은 서로 다른 3개의 Job(jobDetail01, jobDetail02, jobDetail03)을 서로 다른 3개의 Trigger(simpleTrigger01, simpleTrigger02, cronTrigger)에 연관시킨 후, 3개의 Trigger들을 Scheduler에 등록시켜서 Job 스케줄링을 수행시킨 예이다. 수행 시간 조건에 따라 일정 시간 마다 반복적인 일을 하는데, 이때 Scheduler에 TriggerListener(non-global)와 GlobalTriggerListener을 등록하여 Trigger의 수행 순서 모습을 모니터링할 수 있게 하였다.
  • JobDetailBean with Stateless Job
  • 다음은 Spring Framework에서 제공하는 QuartzJobBean을 상속받아서 작성한 Job을 Job 클래스로 가지고 있는 JobDetailBean인 jobDetail01을 정의한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    JobDetailBean의 jobDataAsMap 속성으로 설정한 데이터값은 Job들 사이에 공유되어 사용될 수 있으나 Stateless한 Job이므로 반복 수행되는 Job에서 값을 변경시킬 수는 없다.
    <!-- JobDetailBean -->
    <bean id="jobDetail01"
      class="org.springframework.scheduling.quartz.JobDetailBean">
    	
      <property name="jobClass"
       	value="integration.anyframe.services.scheduling.SimpleQuartzJobBean" />		
      <property name="jobDataAsMap">
        <map>
          <entry>
            <key>
              <value>count</value>
            </key>
            <value>6</value>
          </entry>
        </map>
      </property>
    </bean>	
    
    다음은 Spring Framework에서 제공하는 QuartzJobBean을 상속받아서 작성한 테스트 용 SimpleQuartzJobBean 클래스의 구현 모습이다. Spring의 QuartzJobBean 클래스를 상속받으면 JobExecutionContext를 argument로 갖는 executeInternal 메소드를 작성하여 스케줄링 일정에 따라 반복적으로 수행될 작업을 기술한다. 이 예제에서는 JobDetailBean의 jobDataAsMap 속성으로 지정한 count 값을 변경시켜보려고 하지만, Stateless한 Job이기 때문에 처음 Job 수행 이후 반복 수행되는 Job들에서는 값이 변경되지 않는다.
    public class SimpleQuartzJobBean extends QuartzJobBean {
    	public void setCount(int count) { 
    		this.count=count;	
    	}	
    	protected void executeInternal(JobExecutionContext context)
    			throws JobExecutionException {			
    		this.count= this.count + 1;		
    		context.getJobDetail().getJobDataMap().put("count",this.count);
    		System.out.println("count="+this.count);		
        ...중략   
    
  • JobDetailBean with Stateful Job
  • 다음은 Quartz에서 제공하는 StatefulJob 인터페이스 클래스를 구현하여 작성한 Job을 Job 클래스로 가지고 있는 JobDetailBean인 jobDetail02을 정의한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    JobDetailBean의 jobDataAsMap 속성으로 설정한 데이터값은 Job들 사이에 공유되어 사용될 수 있고, Stateful한 Job이므로 반복 수행되는 Job에서 값을 변경시킬 수 있다.(예제에서 count라는 값을 1씩 증가시키고 있다.)
    <!-- JobDetailBean -->
    <bean id="jobDetail02"
      class="org.springframework.scheduling.quartz.JobDetailBean">
    	
      <property name="jobClass"
       	value="integration.anyframe.services.scheduling.StatefulQuartzJobBean" />		
      <property name="jobDataAsMap">
        <map>
          <entry>
            <key>
              <value>count</value>
            </key>
            <value>6</value>
          </entry>
        </map>
      </property>
    </bean>	
    
    다음은 Quartz에서 제공하는 StatefulJob 인터페이스 클래스를 구현하여 작성한 테스트 용 StatefulQuartzJobBean 클래스의 구현 모습이다. StatefulJob 인터페이스 클래스의 메소드인 execute 메소드를 아래와 같이 JobExecutionContext를 argument로 갖도록 구현하고, 메소드 내부에서는 executeInternal 메소드를 호출하도록 한다. 이 예제에서는 JobDetailBean의 jobDataAsMap 속성으로 지정한 count 값을 변경시켜서 수행되는 동일한 Job들 사이에 값을 공유시키고 있다.
    import org.quartz.StatefulJob;
    public class StatefulQuartzJobBean implements StatefulJob {
    	public void setCount(int count) { 
    		this.count=count;	
    	}	
    	public void execute(JobExecutionContext context)
    			throws JobExecutionException {
    		try {
    			BeanWrapper bw = PropertyAccessorFactory
    					.forBeanPropertyAccess(this);
    			MutablePropertyValues pvs = new MutablePropertyValues();
    			pvs.addPropertyValues(context.getScheduler().getContext());
    			pvs.addPropertyValues(context.getMergedJobDataMap());
    			bw.setPropertyValues(pvs, true);
    		} catch (SchedulerException ex) {
    			throw new JobExecutionException(ex);
    		}
    		executeInternal(context);
    	}
    
    	public void executeInternal(JobExecutionContext context)
    			throws JobExecutionException {
    		this.count = count + 1;
    		context.getJobDetail().getJobDataMap().putAsString("count", count);
    		System.out.println(this.getClass().getName() + "::" + this.count);
    		result = count;
    	}...중략   
    
  • MethodInvokingJobDetailFactoryBean
  • 다음은 Spring Framework에서 제공하는 MethodInvokingJobDetailFactoryBean을 그대로 재사용하여 POJO 형태로 작성한 SimplePOJOJobBean을 Job 클래스로 가지고 있는 JobDetail인 jobDetail03을 정의한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    <bean id="jobDetail03" 
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    	<property name="targetObject" ref="simplePOJOJobBean"/>
    	<property name="targetMethod" value="doSomething"/>
    </bean>
    	
    <!-- MethodInvokingBean -->
    <bean id="simplePOJOJobBean"
        class="integration.anyframe.services.scheduling.SimplePOJOJobBean"/>
    
    다음은 Job 관련 API에 종속되지 않고 POJO로 작성한 SimplePOJOJobBean 클래스의 구현 모습이다. Trigger에 의해서 SimplePOJOJobBean 클래스의 doSomething() 메소드가 수행될 것이다. doSomething() 메소드 내에 스케줄링 일정에 따라 반복적으로 수행될 작업을 기술한다.
    public class SimplePOJOJobBean {
    	public void doSomething(){
    		count = count + 1 ;
    		System.out.println(this.getClass().getName()+"::"+this.count);	
    	} 	
    	...중략
    }
    
  • SimpleTriggerBean for Stateless Job
  • 다음은 Spring Framework에서 제공하는 SimpleTriggerBean를 이용하여 위에서 작성한 JobDetail중 jobDetail01을 설정한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    처음 Job 수행 이후 반복 수행 시간의 간격은 1초로, 반복 회수는 3회로 설정되었으며 해당 trigger에만 적용시킬 triggerListener가 triggerListenerNames 속성 정보로 정의되어 있다. triggerListener Bean 설정은 하위 내용을 참고한다.
    <!-- SimpleTriggerBean -->
    <bean id="simpleTrigger01"
    	class="org.springframework.scheduling.quartz.SimpleTriggerBean">
      <property name="jobDetail" ref="jobDetail01" />
    	<property name="repeatInterval" value="1000"/>
    	<property name="repeatCount" value="3"/>
      <property name="triggerListenerNames" value="triggerListener"/>		    
    </bean>
    
  • SimpleTriggerBean for Stateful Job
  • 다음은 Spring Framework에서 제공하는 SimpleTriggerBean를 이용하여 위에서 작성한 JobDetail중 jobDetail02을 설정한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    하나의 Trigger는 하나의 JobDetail을 설정해야 하므로 위의 SimpleTriggerBean과 동일한 속성 정보를 갖는 Trigger를 추가 작성한다.
    처음 Job 수행 이후 반복 수행 시간의 간격은 1초로, 반복 회수는 3회로 설정되었으며 해당 trigger에만 적용시킬 triggerListener가 triggerListenerNames 속성 정보로 정의되어 있다. triggerListener Bean 설정은 하위 내용을 참고한다.
    <!-- SimpleTriggerBean -->
    <bean id="simpleTrigger02"
    	class="org.springframework.scheduling.quartz.SimpleTriggerBean">
      <property name="jobDetail" ref="jobDetail02" />
    	<property name="repeatInterval" value="1000"/>
    	<property name="repeatCount" value="3"/>
      <property name="triggerListenerNames" value="triggerListener"/>		    
    </bean>
    
  • CronTriggerBean
  • 다음은 Spring Framework에서 제공하는 CronTriggerBean를 이용하여 위에서 작성한 JobDetail중 jobDetail03을 설정한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    아래 예제에서 보면 SimpleTriggerBean과 달리 cron expression을 통해 스케줄링 시간을 설정한 모습을 볼 수 있는데, 현재 2초 간격으로 실행되도록 설정한 것이다.
    <!-- CronTriggerBean -->		
    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
         <property name="jobDetail" ref="jobDetail03" />        
         <property name="cronExpression" value="2 * * * * ?" />
    </bean>
    
  • TriggerListener Beans
  • 다음은 Quartz에서 제공하는 TriggerListener를 구현한 QuartzTriggerListener 클래스를 설정한 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다. 여기서 동일한 QuartzTriggerListener 클래스를 두번 설정하고 있는데, 하나는 특정 Trigger에만 적용시킬 triggerListener로, 나머지 하나는 모든 Trigger에 적용시킬 globalTriggerListener로 분리하여 설정하였다. 이때 2개의 TriggerListener를 런타임시에 구분하기 위해서 globalTriggerListener Bean에는 listenerType 속성을 "Global"로 설정하였다. (미설정 시에는 Non-global로 설정된다.)
    	<!-- TriggerListener -->
    <bean id="triggerListener" 
          class="integration.anyframe.services.scheduling.QuartzTriggerListener"/>
    	
    <bean id="globalTriggerListener" 
          class="integration.anyframe.services.scheduling.QuartzTriggerListener">
       <property name="listenerType" value="Global"/>
    </bean>
    
    다음은 Quartz에서 제공하는 TriggerListener를 구현한 QuartzTriggerListener 클래스 구현 모습이다.
    Trigger가 fire->execute->complete 혹은 misfire되는 시점에 로깅을 남기는 예제이다.
    import org.quartz.TriggerListener;
    public class QuartzTriggerListener implements TriggerListener {
      String listenerType ="Non global";
      public void setListenerType(String listenerType) {
    	  this.listenerType=listenerType;
      }
      public void triggerFired(Trigger trigger, JobExecutionContext ctx) {
        System.out.println(trigger.getJobName()+": triggerFired("+listenerType+")");
      }
      public boolean vetoJobExecution(Trigger trigger, JobExecutionContext ctx) {
        System.out.println(trigger.getJobName()+": vetoJobExecution("+listenerType+")");
        return false;
      }
      public void triggerComplete(Trigger trigger, JobExecutionContext ctx, int arg) {
        System.out.println(trigger.getJobName()+": triggerComplete("+listenerType+")");
      }
      public void triggerMisfired(Trigger trigger) {
        System.out.println(trigger.getJobName()+": triggerMisfired("+listenerType+")");
      }
      public String getName() {
        return "triggerListener";
      }
    
  • SchedulerFactoryBean
  • 다음은 Spring Framework에서 제공하는 SchedulerFactoryBean를 이용하여 위에서 작성한 3개의 Trigger(simpleTrigger01, simpleTrigger02, cronTrigger)와 2개의 TriggerListener(triggerListener, globalTriggerListener)를 등록하는 Spring 속성 정의 파일(applicationContext-scheduling.xml )의 일부이다.
    <!-- SchedulerFactoryBean -->
    <bean id="schedulerFactoryBean"
    	class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    	<property name="triggers">
    		<list>
      		   <ref local="simpleTrigger01" />	
      		   <ref local="simpleTrigger02" />	  		   
      		   <ref local="cronTrigger" />	
    		</list>
    	</property>
    			
    	<property name="triggerListeners" ref="triggerListener"/>	
    	<property name="globalTriggerListeners" ref="globalTriggerListener"/>		
    	
    	<!-- DataSource 설정 시 Job과 Trigger에 관련된 정보가  
    	     자동으로 DB를 통해 관리됨.                       -->
    	<!--<property name="dataSource" ref="dataSource"/>    -->	
    </bean>
    
  • TestCase
  • 다음은 앞서 정의한 속성 설정을 기반으로 하여 schedule를 실행하는 SchedulingServiceTest.java 코드의 일부이다.
    /**
    * Scheduling 서비스를 통해 수행된 Job Object의 수행 결과가 올바른지 알아보는 테스트이다.
    */
    /**
     * SimpleTrigger에 등록된 JobDetailBean의 Stateless QuatzJob을 수행하고 결과 값을 확인한다.
     */		
    public void testSimpleTrigger01() throws Exception {
    	Thread.sleep(1000*5);
    					
    	int result=SimpleQuartzJobBean.getResult();	
    	System.out.println("main >> result of Stateless QuatzJobBean bean :"+	result);
    	
    	if ( result!=7){
    		throw new Exception(" Scheduling Service failed : result is " + result );
    	}		
    }
    
    /**
     * SimpleTrigger에 등록된 JobDetailBean의 Stateful QuatzJob을 수행하고 결과 값을 확인한다.
     */		
    public void testSimpleTrigger02() throws Exception {
    	Thread.sleep(1000*5);
    					
    	int result=StatefulQuartzJobBean.getResult();
    	System.out.println("main >> result of Stateful QuatzJobBean bean :"+ result);
    	
    	if ( result!=10){
    		throw new Exception(" Scheduling Service failed : result is " + result );
    	}		
    }
    
    /**
     * CronTrigger에 등록된 POJO Job을 수행하고 결과 값을 확인한다.
     */		
    public void testCronTrigger() throws Exception {
    	Thread.sleep(1000*50);
    	SimplePOJOJobBean simplePOJOJobBean = 
        ( SimplePOJOJobBean )context.getBean("simplePOJOJobBean");
    	
    	int result= simplePOJOJobBean.getCount();
    	System.out.println("main >> result of simplePOJOJobBean  :"+ result);
    	
    	if ( result <= 0 ){
    		throw new Exception(" Scheduling Service failed ");
    	}
    }	
    

Resources