다음은 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 |