본문 바로가기
Back-end/JAVA

[JAVA] Quartz 스케줄러 만들기 (2) - Listener

by 허도치 2020. 11. 10.
서론

  이전 포스트에서는 간단하게 Quartz를 이용한 스케줄러를 만들고 실행해보았다. 이번 포스트에서는 Listener에 대해서 알아볼 계획이다. Listener는 주로 프로세스의 실행, 종료, 중단 등의 라이프사이클에 따라 로그를 출력하거나 추가 로직을 작성할 수 있는 객체인데, 굳이 설정하지 않아도 실행하는데 문제가 되지는 않는다.

 

  Quartz에서는 ScheduleListener, TriggerListener, JobListener 이렇게 총 3가지가 있는데, ScheduleListener는 너무 세분화되어 있어서 생략하고, Trigger와 Job Listener에 대해서 다루어 보려고 한다.

 

 

 

개발환경

    - jdk-11.0.5
    - quartz-2.3.2

<dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>2.3.2</version>
</dependency>

  

 

 

소스코드
1. MyTriggerListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerListener;
 
public class MyTriggerListener implements TriggerListener {
 
    public static final String EXECUTION_COUNT = "EXECUTION_COUNT";
    
    public String getName() {
        return MyTriggerListener.class.getName();
    }
    
    /**
     * Trigger가 실행된 상태
     * 리스너 중에서 가장 먼저 실행됨
     */
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        System.out.println(String.format("\n[%-18s][%s]""triggerFired", trigger.getKey().toString()));
    }
 
    /**
     * Trigger 중단 여부를 확인하는 메소드
     * Job을 수행하기 전 상태
     * 
     * 반환값이 false인 경우, Job 수행
     * 반환값이 true인 경우, Job을 수행하지않고 'SchedulerListtener.jobExecutionVetoed'로 넘어감
     * 
     * Job 실행횟수가 3회이상이면 작업중단
     */
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        JobDataMap map = context.getJobDetail().getJobDataMap();
        int executeCount = -1;
        if (map.containsKey(EXECUTION_COUNT)) {
            executeCount = map.getInt(EXECUTION_COUNT);
        }
        System.out.println(String.format("[%-18s][%s]""vetoJobExecution", trigger.getKey().toString()));
        
        return executeCount >= 3;
    }
 
    /**
     * Trigger가 중단된 상태
     */
    public void triggerMisfired(Trigger trigger) {
        System.out.println(String.format("[%-18s][%s]""triggerMisfired", trigger.getKey().toString()));
    }
 
    /**
     * Trigger가 완료된 상태
     */
    public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) {
        System.out.println(String.format("[%-18s][%s]""triggerComplete", trigger.getKey().toString()));
    }
}
cs

 

 

 

2. MyJobListener.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
 
public class MyJobListener implements JobListener{
 
    @Override
    public String getName() {
        return MyJobListener.class.getName();
    }
 
    /**
     * Job이 수행되기 전 상태
     *   - TriggerListener.vetoJobExecution == false
     */
    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        System.out.println(String.format("[%-18s][%s] 작업시작""jobToBeExecuted", context.getJobDetail().getKey().toString()));
    }
 
    /**
     * Job이 중단된 상태
     *   - TriggerListener.vetoJobExecution == true
     */
    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        System.out.println(String.format("[%-18s][%s] 작업중단""jobExecutionVetoed", context.getJobDetail().getKey().toString()));
    }
 
    /**
     * Job 수행이 완료된 상태
     */
    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        System.out.println(String.format("[%-18s][%s] 작업완료""jobWasExecuted", context.getJobDetail().getKey().toString()));
    }
}
 
cs

 

 

 

3. SampleJobExecutor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import java.text.SimpleDateFormat;
import java.util.Date;
 
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.ListenerManager;
import org.quartz.PersistJobDataAfterExecution;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
 
/**
 * Quartz StatefulJob
 *   - Job이 실행중인 경우, 실행된 Job이 완료된 후에 다음 Job을 수행
 */
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class SampleJobExecutor implements Job {
    
    private static final SimpleDateFormat TIMESTAMP_FMT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSSS");
    public static final String EXECUTION_COUNT = "EXECUTION_COUNT";
    
    @Override
    public void execute(JobExecutionContext ctx) throws JobExecutionException {
        JobDataMap map = ctx.getJobDetail().getJobDataMap();
        String currentDate = TIMESTAMP_FMT.format(new Date());
        String message = map.getString("message");
 
        int executeCount = 0;
        if (map.containsKey(EXECUTION_COUNT)) {
            executeCount = map.getInt(EXECUTION_COUNT);
        }
        executeCount += 1;
        map.put(EXECUTION_COUNT, executeCount);
        
        System.out.println(String.format("[%-18s][%d][%s] %s""execute", executeCount, currentDate, message ));
    }
}
 
/**
 * Quartz Scheduler 실행
 */
class JobLuacher {
    public static void main(String[] args) {
        try {
            // Scheduler 생성
            SchedulerFactory factory = new StdSchedulerFactory();
            Scheduler scheduler = factory.getScheduler();
            
            // Listener 설정
            ListenerManager listenrManager = scheduler.getListenerManager(); 
            listenrManager.addJobListener(new MyJobListener());
            listenrManager.addTriggerListener(new MyTriggerListener());
            
            // Scheduler 실행
            scheduler.start();
 
            // JOB Executor Class
            Class<extends Job> jobClass = SampleJobExecutor.class;
            
            // JOB Data 객체 생성
            JobDataMap jobDataMap = new JobDataMap();
            jobDataMap.put("message""Hello, Quartz!!!");
            jobDataMap.put(SampleJobExecutor.EXECUTION_COUNT, 0);
 
            // JOB 생성
            JobDetail jobDetail = JobBuilder.newJob(jobClass)
                                    .withIdentity("job_name""job_group")
                                    .setJobData(jobDataMap)
                                    .build();
            
            // SimpleTrigger 생성
            // 5초마다 반복하며, 최대 10회 실행
            SimpleScheduleBuilder simpleSch = SimpleScheduleBuilder.simpleSchedule()
                                                .withRepeatCount(10)
                                                .withIntervalInSeconds(5);
            SimpleTrigger simpleTrigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                                            .withIdentity("simple_trigger""simple_trigger_group")
                                            .withSchedule(simpleSch)
                                            .forJob(jobDetail)
                                            .build();
 
            // Schedule 등록
            scheduler.scheduleJob(jobDetail, simpleTrigger);
 
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
cs

 

 

 

실행결과

  - Job이 3회 이상 수행되면 '작업중단'이 되는 것을 확인 할 수 있음.

 

 

 

마치며

  지금까지 Quartz Scheduler에 Listener를 달아보았다. 아직까지는 로그를 남기는 용도로만 사용하고 있는데 어떻게 활용해볼지 고민중이다. 사실 Quartzs는 크롤러를 주기적으로 실행할 목적으로 만들기었기 때문에 Listener까지는 필요가 없었지만, StatefulJob을 알아보던 중에 로그를 출력해가며 어떻게 돌아가는지 파악하려고 적용해봤다.

댓글