두번째, 스프링 배치보다 간편한 스프링 Quartz

2편 마지막편!!! 두둥~

이번 편은, WAS 구동 중에 Job 시작/중지를 시켜보는 동적으로 하는 방법을 알아보자!

 지난 편에는 WAS 시작(Start)할 때 크론 표현식(cron expression)가 Job 사용 여부를 가져와서

“org.springframework.scheduling.quartz.SchedulerFactoryBean”에 등록했었죠?

기억 하시나요? ^^;; 기억 안 나셔도 괜찮습니다.
지난 편 보고 천천히 오세요~

 “SchedulerFactoryBean”을 상속해서 “MySchedulerFactoryBean”을 스프링 빈(Spring Bean)으로 등록했었습니다.

<bean id="schedulerFactory" class="kr.co.jaeger.MySchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="oneTrigger" />
                <ref bean="twoTrigger" />
                <ref bean="threeTrigger" />
            </list>
        </property>
    </bean>
</bean>

 이제 우리가 하고자 하는 것은 WAS를 shutdown 하지 않고, Quartz Job을 동작 중인 것을 중지하거나, 중지된 것을 재시작하는 것을

해 보려고 합니다. 물론, 크론 표현식도 변경해서 말이죠!

원리는 이렇습니다.
“MySchedulerFactoryBean”에 새로운 트리거를 주입(DI, Dependency Injection)를 해주는 겁니다.

 예를 들어서, “oneTrigger”가 있습니다.

“oneTrigger”가 가지고 있는 것은 2가지입니다.

<bean id="oneTrigger" class="kr.co.jaeger.MyCronTriggerBean">
    <property name="jobDetail" ref="oneService" />
    <property name="cronExpression" value="0 0 1 * * ?" />
</bean>

 1번째는 잡(JobDetail)
2번째는 크론 표현식(Cron Expression)

트리거 역활은 어떤 잡(Job)을 어떤 시간(Cron Expression)에 수행할 것인가?
이것입니다.

 트리거(trigger)란?

총의 방아쇠를 뜻하는 사격 용어로서, 어떤 사건의 반응/사건을 유발한 계기나 도화선의 의미를 말합니다.
연쇄반응을 일으키는 촉매제라고 할 수 있죠~

 일반적으로 잡(Job) 변경하려면, 소스 검수도 받아야 하고 자바 클래스도 반영해야 합니다.

대개 수정이나 변경이 잘 일어나지 않죠?
그리고 운영 중인 서비스에 관리자 POC(Point Of Contact)에서 자바 클래스를 맘대로 올릴수 있게 하지도 않겠죠?

자바 클래스도 동적으로 변경하고 싶다면 스프링 배치(Spring Batch) 프로젝트를 이용해 보세요 ㅠㅜ
그거까지 원하신다면 여기 잘못 찾아오셨습니다. 이히히~

 저는 운영중인 배치 스캐줄러의 시작/중지와 크론 표현식 변경에 초점을 맞추었습니다.

다시 본론으로 돌아가죠^^

<bean id="oneService" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="oneJobService" />   
    <property name="targetMethod" value="execute" />
    <property name="concurrent" value="false" />
</bean>

<bean id="oneTrigger" class="kr.co.jaeger.MyCronTriggerBean">
    <property name="jobDetail" ref="oneService" />
    <property name="cronExpression" value="0 0 1 * * ?" />
</bean>

 
여기서 스프링의 마법인 자바 리플렉션(java reflection)을 보실수 있습니다.
너무 거창했나요? ㅋㅋ

 한번 빠져봅시다.

 스프링에서 ApplicationContextAware 이녀석 많이 보셨죠?
제가 참 좋아하는 녀석입니다. 

String configLocation = "META-INF/spring/app-context.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configLocation);
OneJobService oneService = (OneJobService) context.getBean("oneService");

 스프링 프레임워크로 개발하다가 보면 Bean Id로 ApplicationContext 객체를 얻어서 스프링 빈(Bean)을 가져오는 것을

경험해 보신 분들이 있을 겁니다.

“ApplicationContextAware” 이 녀석을 구현하면 스프링 빈 가져오는 것은 식은 죽 먹기죠!! ㅋㅋ
아래처럼 구현해서 유틸성으로 공통으로 사용하면 아주 좋습니다. 좋구요!

@Service("springContextUtil")
public class SpringContextUtil implements ApplicationContextAware {
    private static final Log LOG = LogFactory.getLog(SpringContextUtil.class);
    private static ApplicationContext ctx;

    public ACOMContextUtil() {
        LOG.info("init SpringApplicationContext");
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        ctx = context;
    }

    /**
     * 스프링 빈 조회
     * 
     * @param beanName 스프링빈 이름
     * @return
     */
    public static Object getBean(String beanName) {
        return ctx.getBean(beanName);
    }

    /**
     * 스프링 빈 조회
     * 
     * @param beanName 스프링빈 이름
     * @param requiredType 클래스 타입
     * @return
     */
    public static <T> T getBean(String beanName, Class<T> requiredType) {
        return ctx.getBean(beanName, requiredType);
    }
}

 
이제 원하는대로 스프링에서 트리거(trigger)와 잡(job)을 가져올 수 있게 되었습니다.
오예~ 이제 절반 이상한 겁니다.

WAS 운영 중에 동작 중인 잡(Job)을 중지하고 싶다면 “MySchedulerFactoryBean”에서 해당 트리거(trigger)를 스케줄러팩토리빈(MySchedulerFactoryBean)에서
삭제하시면 됩니다. 두둥~;;;;

중지했던 잡(Job)을 다시 재시작하고 싶다면 스케줄러팩토리빈(MySchedulerFactoryBean)에 추가해 주시면 됩니다 ;;;;;
참 쉽죠? ㅠㅜ

위에 말했던 것처럼 트리거는 2가지 프로퍼티를 가지고 있습니다.
첫번째는 잡(Job), 두번째는 크론 표현식(cron expression)입니다.

첫번째는 프로퍼티는 잡(Job)이기 때문에 ApplicationContext 을 이용해서 스프링 빈(Bean) 가져올 수 있겠죠?
두번째도 마찬가지로 크론 표현식으로 변경해서 주입(DI, Dependency Injection)하면 됩니다.

제가 말이 너무 길었죠?
저도 개발자니까 소스 코드로 말하겠습니다. 

@Service("batchService")
public class BatchServiceImpl implements BatchService {
    private static final Log Log = LogFactory.getLog(BatchServiceImpl.class);

    /** 스케줄러 */
    @Resource(name="schedulerFactory")
    private Scheduler scheduler;

    /**
     * 배치 갱신
     * 
     * @param triggerName 트리거명
     * @param useCls 수행 여부
     * @param cronExpression 크론 표현식
     * @throws Exception
     */
    @Override
    public void setExecuteBatch(String triggerName, String useCls, String cronExpression) throws Exception {
        String newTriggerName = triggerName + "Trigger";
        String newJobDetailName = triggerName;

        Log.info("triggerName : " + newTriggerName);
        Log.info("useCls : " + useCls);

        CronTriggerBean newTrigger = (CronTriggerBean) ACOMContextUtil.getBean(newTriggerName);
        newTrigger.setCronExpression(cronExpression);

        JobDetail newJobDetail = (JobDetail) ACOMContextUtil.getBean(newJobDetailName);

        // 트리거 존재하지 않을 때
        if (scheduler.getTrigger(newTriggerName, Scheduler.DEFAULT_GROUP) == null) {
            if ("Y".equalsIgnoreCase(useCls)) {
                Log.info("  트리거 신규 추가, 수행(Y)");

                // 스케줄에 트리거 추가
                scheduler.scheduleJob(newJobDetail, newTrigger);
            } else {
                Log.info("  트리거 미 존재, 수행(N)");
            }
        }
        // 트리거 존재할 때
        else {
            if ("Y".equalsIgnoreCase(useCls)) {
                Log.info("  해당 트리거 수행(Y)");

                scheduler.deleteJob(newJobDetailName, Scheduler.DEFAULT_GROUP);
                scheduler.scheduleJob(newJobDetail, newTrigger);
            } else {
                Log.info("  해당 트리거 수행(N)");

                scheduler.deleteJob(newJobDetailName, Scheduler.DEFAULT_GROUP);
            }
        }
    }

}

일단 트리거(trigger)가 존재하는지 체크하는 것은 스케줄러팩토리빈(MySchedulerFactoryBean)에 존재하는지 체크하는 것입니다.
해당 트리거 이름으로 트리거를 가져왔는데 없다면 등록되어 있지 않다는거죠?
그래서, 새롭게 트리거를 추가해 주는겁니다. ^^ㅎㅎㅎ

트리거가 존재한다면 기존 트리거 삭제할 수도 있어요!
해당 잡(Job)을 사용 안함으로 하겠다면 등록된 트리거 삭제하면 되겠죠?
해당 잡(Job) 의 크론 표현식을 변경하고 싶다면 등록된 트리거를 삭제하고, 크론 표현식 변경된 트리거를 새로 등록하면 주면 되는 원리입니다.

참 쉽죠?? ㅎㅎㅎ
너무 구구절절 딴길로 많이 샜지만 여기까지 읽어주셔서 감사합니다.

개발자가 우뚝 서는 그날까지 여러분 즐거운 코딩하세요!!