IT/Java

[Quartz] Quartz 스케줄러 적용의 건

어린이개발자 2023. 1. 13. 12:26

다음은 Quartz 스케줄러 적용 관련한 글이다.

 

1. 배경

스케줄링 방식을 "스프링의 @Scheduled 어노테이션 사용" 에서 "Quartz 라이브러리 사용" 으로의 변경에 대한 검토를 하기 위해 진행하였다.

2. 개발 환경

개발 환경은 다음과 같다.

  • Spring Framework 4.1.2
  • quartz-2.2.3 library

3. 전체 구조

  • context-quartz.xml : schedulerFactoryBean 생성 후 jobDetail 선언
  • SchedulerInit.java : 새로운 schedule 생성
  • quartzMainJob.java : schedule 실행 로직 호출
  • JobDetailFactoryBean.java : context-quartz.xml 에서 jobDetail 만들 때 사용

4. 개발 소스

  4.1. 필요 파일 Setting
   1) Quartz 관련 라이브러리 추가

       (src > main > webapp > WEB-INF > lib 에
       다운로드 받은 (1) quartz-2.2.3.jar, (2) quartz-jobs-2.2.3.jar 파일 위치)
   2) pom.xml 파일에 내용 추가

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

   3) src > main > resources > spring 에

       context-quartz.xml 파일 생성 후 위치

      (+ applicationContext.xml 파일에서 해당 파일을 import 하도록 설정)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- jobDetail 설정 -->
    <bean name="Job1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    	<property name="jobClass">
        	<value>com.hello.schedules.action.SchedulerInit</value>
        </property>
    </bean>
    
    <!-- schedule trigger 설정 -->
    <!-- trigger 에 jobDetail 을 담아 schedulerFactoryBean 에 올린다 -->
    <bean id="cronTrigger class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    	<property name="jobDetail">
        	<ref bean="Job1" />
        </property>
		<property name="cronExpression">
        	<!-- cron 표현식으로 스케줄러 실행 주기 설정 -->
            <value>0 0/1 * * * ?</value> <!-- 매 1분마다 실행 -->
        </property>
    </bean>
    
    <!-- quartz 실행 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    	<property name="triggers">
            <list>
            	<ref bean="cronTrigger" />
            </list>
        </property>
        <!-- schedulerFactoryBean 안에서 사용할 Service Setting -->
        <!-- @Service 어노테이션으로 등록된 Service 등록 -->
        <property name="schedulerContextAsMap">
        	<map>
            	<entry key="helloService" value-ref="helloService" />
            </map>
        </property>
    </bean>

</beans>

 

   4) web.xml 파일에 내용 추가

<!-- Quartz setting -->
    <servlet>
        <servlet-name>QuartzInitializer</servlet-name>
        <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
        <init-param>
            <param-name>quartz:config-file</param-name>
            <param-value>classpath:/quartz.properties</param-value> /* 파일 위치 설정 */
        </init-param>
        <init-param>
            <param-name>shutdown-on-unload</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>start-scheduler-on-load</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
<!-- Quartz-run -->
    <servlet>
        <servlet-name>SchedulerInit</servlet-name> /* Servlet 이름 설정 */
        <servlet-class>com.hello.schedules.action.SchedulerInit</servlet-class> /* 파일 위치 설정 */
        <load-on-startup>3</load-on-startup>
    </servlet>
<!-- Quartz run Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>SchedulerInit</servlet-name> 
        <url-pattern>/SchedulerInit</url-pattern> /* 파일 내 메서드 이름 설정 */
    </servlet-mapping>

  4.2. 스케줄러 실행

    #  com.hello.schedules.action 패키지 생성 후 (패키지 이름은 임의로 설정)

    1) SchedulerInit.java

package com.hello.schedules.action;

import static org.quartz.CronScheduleBuilder.cronSchedule;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class SchedulerInit extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    	// 실행되는 schedule 정보 확인
        Scheduler scheduler = context.getScheduler();
        Set<JobKey> allJobKeys = null;
        try {
            allJobKeys = scheduler.getJobKeys(GroupMatcher.anyGroup());
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        
        // 새로 추가되어야 할 스케줄 확인 후 추가
        Map parameterMap = new HashMap();
        try {
            // DB에 저장된 scheduler jobDetail 정보 조회 (로직은 여기선 생략)
            // 아래에 정의한 addSchedule 함수 호출
            addSchedule(scheduler);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // setting jobDetail 사용 중지
        try {
            scheduler.unscheduleJob(TriggerKey.triggerKey("cronTrigger"));
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
    
    // Schedule 등록 함수
    public static void addSchedule(Scheduler scheduler) throws SchedulerException {
        String jobName = "job1";
        String triggerName = "trigger1";
        String cronExpression = "0 0/1 * * * ?";
        
        // JobDetail 설정
        JobDetail jobDetail = JobBuilder.newJob(quartzMainJob.class)
                                .withIdentity(jobName)
                                .build();
        
        // Trigger 설정 (여기선, cronTrigger만 다룸)
        Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(triggerName)
                            .withSchedule(cronSchedule(cronExpression))
                            .forJob(jobDetail)
                            .build();
        // schedule 등록
        scheduler.scheduleJob(jobDetail, trigger);
    }
}

    2) quartzMainJob.java

package com.hello.schedules.action;

import javax.annotation.Resource;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import com.hello.service.AService;

public class quartzMainJob extends QuartzJobBean {
    // 실행할 서비스 
    @Resource(name = "aService")
    private AService aService;
    
    // setter
    public void setAService(AService aService) {
    	this.aService = aService;
    }
    
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    	// 스케줄링 하려는 서비스 로직 실행
        this.aService.sendInfo(); /* 하나의 예시 */
    }
}

 

    3) quartzMainJob.java

package com.hello.schedules.action;

import javax.annotation.Nullable;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public abstract class JobDetailFactoryBean implements FactoryBean<JobDetail>, BeanNameAware, ApplicationContextAware, InitializingBean {
	@Nullable
    private String name;
    
    @Nullable
    private String group;
    
    @Nullable
    private Class<? extends Job> jobClass;
    
    private JobDataMap jobDataMap = new JobDataMap();
    
    private boolean durability = false;
    
    private boolean requestsRecovery = false;
    
    @Nullable
    private String description;
    
    @Nullable
    private String beanName;
    
    @Nullable
    private ApplicationContext applicationContext;
    
    @Nullable
    private String applicationContextJobDataKey;
    
    @Nullable
    private JobDetail jobDetail;
}

5. 한계점

  • 번거로움
    • context-quartz.xml, quartzMainJob.java 에 스케줄링 하려는 서비스 파일을 일일이 등록한 후 Server 를 내렸다 올려야 하는 과정이 필요함

6. 개선 방향

  • 스케줄링 목적으로 호출하는 서비스 파일들을 건별로 등록하지 않고 호출만 하여도 바로 스케줄 등록이 되는 방향으로 수정

'IT > Java' 카테고리의 다른 글

[Java] JVM의 Garbage Collector  (0) 2023.05.21
[JUnit] TDD를 편하게 하기 위한 JUnit 사용법  (0) 2023.03.25
2021-03-13 일지1  (0) 2021.03.13
2021-03-11 일지1  (0) 2021.03.11
2021-03-10 일지3  (0) 2021.03.10