Quartz與Spring整合進行熱部署的實現(一)

先來幾張實現圖html

任務管理頁java

新建任務管理.目前實現叫簡單的需求...若各位同窗要實現複雜的設計...quartz都有提供強大的支持.小弟目前的需求作到這已經夠用了.web

接下來.咱們如何實現quartz的熱部署編碼呢?spring

小弟是利用spring整合quzrtz實現的.可是會產生兩個小問題.數據庫

咱們先看看quartz如何與spring整合express

<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/>
        <property name="configLocation" value="classpath:quartz.properties"/><!--
        這個是必須的,QuartzScheduler 延時啓動,應用啓動完後 QuartzScheduler 再啓動 
        -->
        <property name="startupDelay" value="30"/><!--
        這個是可選,QuartzScheduler 啓動時更新己存在的Job,這樣就不用每次修改targetObject後刪除qrtz_job_details表對應記錄了
        -->
        <property name="overwriteExistingJobs" value="true"/>
        <property name="jobDetails" >
            <list>
                <ref bean="xxxJobDetail"/>
                
            </list>    
        </property>            
    </bean>

首先.咱們須要添加一個數據源給quzrtz.容許其序列化JobDetail到數據庫.以後有註釋.呵呵.我就沒必要多說了.服務器

<bean id="xxxJobDetail" class="frameworkx.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!--shouldRecover屬性爲true,則當Quartz服務被停止後,再次啓動任務時會嘗試恢復執行以前未完成的全部任務-->
        <!--<property name="shouldRecover" value="true"/>-->
        <!-- 標識job是持久的,刪除觸發器的時候不被刪除 -->
        <property name="durable" value="true"/>
        <property name="targetObject" ref="xxxxJob"/>
       <!-- 此處是須要執行的任務的方法 -->
        <property name="targetMethod" value="executeJob"/>
    </bean>

凡是使用過quartz跟spring整合的同窗會發現.爲何class的命名空間不是org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean呢?app

由於spring技術小組的class會報NotSerializableException.且功能不強shouldRecover,durable這些基礎屬性不提供...大概spring的MethodInvokingJobDetailFactoryBean根本不支持JobDetail的序列化...想詳細瞭解緣由.能夠看spring的源碼.目前小弟使用的spring3,spring小組依然沒解決這問題,應該說還不支持JobDetail序列化.但國外牛人們已經幫咱們解決好了.詳細見下鏈接dom

http://jira.springframework.org/browse/SPR-3797ide

好了.接下來咱們須要配置quzrtz的properties(放到classpath下.quzrtz就能找到)

org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 60000

#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#org.quartz.jobStore.useProperties = true
org.quartz.jobStore.tablePrefix = QRTZ_  
org.quartz.jobStore.isClustered = false  
org.quartz.jobStore.maxMisfiresToHandleAtATime=1

咱們此次是選擇org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

而不是默認的org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore簡單存儲在內存中.

接來下,咱們須要一個quartz的scheduler管理類.

protected final Log log = LogFactory.getLog(getClass());
    private Scheduler scheduler;
    private QuartzDao quartzDao;
    
    private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceImpl.class);


    
    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    
    public void setQuartzDao(QuartzDao quartzDao) {
        this.quartzDao = quartzDao;
    }

    /**
     * 根據job的名稱獲取job,進而添加到trigger
     * @param name
     * @param jobName
     * @param cronExpression
     * @param group
     * @throws SchedulerException 
     */
    
    public void schedule(String name, String jobName, String cronExpression,String group) throws SchedulerException {
        if (name == null || name.trim().equals("")) {
            name = UUID.randomUUID().toString();
        }

        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobName, group);
            
            if(jobDetail != null) {
                scheduler.addJob(jobDetail, true);

                CronTrigger cronTrigger = new CronTrigger(name, group, jobDetail.getName(),
                        Scheduler.DEFAULT_GROUP);
                cronTrigger.setCronExpression(new CronExpression(cronExpression));
                scheduler.scheduleJob(cronTrigger);
                scheduler.rescheduleJob(cronTrigger.getName(), cronTrigger.getGroup(), cronTrigger);
            }else
                log.error("沒法找到對應的job.因此沒法創建trigger");
            
            
        } catch (SchedulerException e) {
            log.error(e.getMessage());
            throw new SchedulerException();
        } catch (ParseException e) {
            log.error(e.getMessage());
        }
    }
    
    public List<Map<String, Object>> getQrtzTriggers(){
        return quartzDao.getQrtzTriggers();
    }
    
    
    public void pauseTrigger(String triggerName,String group) throws SchedulerException{        
        try {
            scheduler.pauseTrigger(triggerName, group);//中止觸發器
        } catch (SchedulerException e) {
            log.error(e.getMessage());
            throw new SchedulerException();
        }
    }
    
    
    public void resumeTrigger(String triggerName,String group) throws SchedulerException{        
        try {
            scheduler.resumeTrigger(triggerName, group);//重啓觸發器
        } catch (SchedulerException e) {
            log.error(e.getMessage());
            throw new SchedulerException();
        }
    }
    
    
    public boolean removeTrigdger(String triggerName,String group) throws SchedulerException{        
        try {
            
            scheduler.pauseTrigger(triggerName, group);//中止觸發器
            return scheduler.unscheduleJob(triggerName, group);//移除觸發器
        } catch (SchedulerException e) {
            log.error(e.getMessage());
            throw new SchedulerException();
        }
    }
    
    
    public String[] getJobNames(String group) throws SchedulerException {
        String[] jobNames = null;
        try {
            jobNames = scheduler.getJobNames(group);
        } catch (SchedulerException e) {
            log.error(e.getMessage());
            throw new SchedulerException();
        }
        return jobNames;
    }

但前只是簡單實現經過cornexpression.如有複雜配置trigger規則的.或須要對trigger添加calendar...能夠本身進行擴展.

一下是quzrtzDao的實現

private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public List<Map<String, Object>> getQrtzTriggers() {
        List<Map<String, Object>> results = getJdbcTemplate().queryForList("select * from QRTZ_TRIGGERS order by start_time");
        long val = 0;
        String temp = null;
        for (Map<String, Object> map : results) {
            temp = MapUtils.getString(map, "trigger_name");
            if(StringUtils.indexOf(temp, "#") != -1){
                map.put("display_name", StringUtils.substringBefore(temp, "#"));
            }else{
                map.put("display_name", temp);
            }
            
            val = MapUtils.getLongValue(map, "next_fire_time");
            if (val > 0) {
                map.put("next_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
            }

            val = MapUtils.getLongValue(map, "prev_fire_time");
            if (val > 0) {
                map.put("prev_fire_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
            }

            val = MapUtils.getLongValue(map, "start_time");
            if (val > 0) {
                map.put("start_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
            }
            
            val = MapUtils.getLongValue(map, "end_time");
            if (val > 0) {
                map.put("end_time", DateFormatUtils.format(val, "yyyy-MM-dd HH:mm:ss"));
            }
            
            map.put("statu",status.get(MapUtils.getString(map, "trigger_state")));
        }

        return results;
    }
    
    
    public static final Map<String,String> status = new HashMap<String,String>();
    
    
    static{
        status.put("ACQUIRED", "運行");
        status.put("PAUSED", "暫停");
        status.put("WAITING", "等待");
         
    }
    
    private JdbcTemplate getJdbcTemplate() {
        return new JdbcTemplate(this.dataSource);
    }

同窗們能夠根據本身的風格,修改一下其代碼.

此時咱們就能夠建立一個簡單的quzrtz的熱部署管理.

我以前講過會有兩個問題.第二個問題是什麼呢?還記得講過咱們須要講jobDetail序列化到數據庫嗎?由於咱們使用了spring 系統中的manager類經過spring的IOC依賴注入.那咱們的跟quartz相關的manager都須要聲明實現Serializable序列化接口.

此時咱們能夠想到用ContextLoader.getCurrentWebApplicationContext()把相應的manage經過bean id get出來.

public class SpringBeanProvide implements Serializable/*, ApplicationContextAware*/{


    /**
     * 
     */
    private static final long serialVersionUID = 8430477279431126488L;
    private ApplicationContext context;
    
    @SuppressWarnings("unchecked")
    public <T> T getBean(Class<T> clazz, String beanName){
        context = ContextLoader.getCurrentWebApplicationContext();
        return (T)context.getBean(beanName);
    }

    public ServletContext getServletContext() {
        WebApplicationContext webContext = ContextLoader.getCurrentWebApplicationContext();
        return webContext.getServletContext();
    }
    
    /*@Autowired
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        this.context = context;
        
    }*/
}

咱們看看ContextLoader.getCurrentWebApplicationContext()的源碼

/**
     * Obtain the Spring root web application context for the current thread
     * (i.e. for the current thread's context ClassLoader, which needs to be
     * the web application's ClassLoader).
     * @return the current root web application context, or <code>null</code>
     * if none found
     * @see org.springframework.web.context.support.SpringBeanAutowiringSupport
     */
    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }

currentContextPerThread是一個final的ConcurrentHashMap.也是IOC容器存儲bean的一個hash表.

到底何時才把ccl設置到currentContextPerThread中呢?

咱們往上看源碼

try {
            // Determine parent for root web application context, if any.
            ApplicationContext parent = loadParentContext(servletContext);

            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            this.context = createWebApplicationContext(servletContext, parent);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

其中...currentContextPerThread.put(ccl, this.context);是在ApplicationContext parent = loadParentContext(servletContext);之下.

ContextLoader.getCurrentWebApplicationContext()只能在容器徹底初始化後才能使用..這對於單元測試時...ccpt返回出來的是一個null...

這就是我爲何會在SpringBeanProvide出現註釋了ApplicationContextAware的緣由...由於本人有點懶...哈哈..沒從新整理一份spring的bean xml做爲單元測試使用...因此...在單元測試時...改一下注釋...就去跑了..呵呵...此時...咱們已經可使用quzrtz的熱部署.沒必要重啓服務器就能夠對任務進行管理了.

以上的實現.參考了javaeye中的部分文章.

 

本文轉自:http://www.cnblogs.com/pigwing/archive/2011/04/04/2005158.html

相關文章
相關標籤/搜索