您的位置:首页 > 其它

Quartz的简化(只要一张表,动态启停任务)

2016-01-04 14:07 309 查看
项目中有模块依赖到了Quartz来做定时任务。那天和大师研究了一下午,讲一个使用这个工具的一些收获。

首先,用的不是原先的Quartz,而是与spring整合的。需要用到Spring-Conte-Support-4.2.3.Release.jar,Quartz-2.2.2.jar。使用的方式如下

<pre name="code" class="html"><pre name="code" class="html"><pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?>

<beans> 
  <!-- 定时清理 MessageRelation和hadsend Map   1 -->  
  <bean id="clearRelationJob" class="com.yicong.kisp.job.ClearRelationAndHadsendJob"/>  
  
  <!-- JobDetajil,基于MethodInvokingJobDetailFactoryBean调用普通Spring Bean  2 -->  
  <bean id="clearRelationJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 
    <property name="targetObject" ref="clearRelationJob"/>  
    <property name="targetMethod" value="doClear"/>  
    <!-- 同一任务在前一次执行未完成而Trigger时间又到时是否并发开始新的执行, 默认为true. -->  
    <property name="concurrent" value="false"/> 
  </bean>  
  <!-- Cron式Trigger定义   3 -->  
  <bean id="clearRelationJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> 
    <property name="jobDetail" ref="clearRelationJobDetail"/>  
    <property name="misfireInstruction" value="2"/>  
    <!-- 全年、周2,4,6、00:01:01 -->  
    <property name="cronExpression" value="1 1 0 ? 1-12 2,4,6 *"/>  
    <!-- 延迟10秒启动 -->  
    <property name="startDelay" value="10000"/> 
  </bean>  
  <!-- 调度器 4 -->  
  <bean id="schedulerFactoryBean" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> 
    <property name="triggers"> 
      <list> 
        <ref bean="clearRelationJobTrigger"/> 
      </list> 
    </property> 
  </bean> 
</beans>




使用xml文件进行配置,都是“四段式”的配置方法。

1是自己写的来做具体逻辑处理的类

2是JobDetail,MethodInvokingJobDetailFactoryBean是spring对Quartz的JobDetail的包装,在它里面,定义了一个来自org.quartz包的JobDetail。

public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker
		implements FactoryBean<JobDetail>, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean {

	private String name;
	private boolean concurrent = true;
	private String targetBeanName;
	private String beanName;
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
	private BeanFactory beanFactory;

	private JobDetail jobDetail;
......
spring中大量用到了FactoryBean,这个接口的说明:

Interface to be implemented by objects used within a
BeanFactory
which are themselves factories. If a bean implements this interface, it is used as a factory for an object to expose, not directly
as a bean instance that will be exposed itself.

NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but the object exposed for bean references (
getObject()
is always
the object that it creates.

大意是说一个bean实现了它,通常用来作为一个向外暴露对象的工厂,而非直接得到它本身,向外暴露的对象由泛型指定,通过覆盖的getObject()返回真正的对象。

This interface is heavily used within the framework itself, for example for the AOP
org.springframework.aop.framework.ProxyFactoryBean
or the
org.springframework.jndi.JndiObjectFactoryBean
.
It can be used for application components as well; however, this is not common outside of infrastructure code.

本接口被框架本身大量使用,如果AOP‘动态代理方面。然而在基础代码外少见。

2中的targetObject、targetMethod指定了类与方法,还有一些属性配置未写出来。

2中的JobDetail准备完了,就可以被3引用了。3用来配置一个基于Cron的触发器。也是用到了FactoryBean。Trigger的作用在于根据Cron中的设置,定时触发job,所以还有concurrent、misfireInstructiont等可以配置。

4中把Trigger注册到Scheduler中,Scheduler配置线程池的大小。一个Job运行时消耗一个线程。

就这么多。就开始就这么用,感觉也还可以,但是改了什么属性就要重启应用,显得不灵活方便。虽然Quartz也有基于数据库表的,但是配套的表有十多张,有些用不上也不能删。想着业务表才十多张,Quartz也要这么多,很不舒服。

最方便的做法就是:

1、把必须要的JobDetail和Trigger的属性移到一张表中,在应用启动后自动读取表数据,初始化job并运行。

2、在管理页面中可以控制Job的运行、停止状态。

That's all。

那就要进行相应的改造了。由于已经用了spring,不考虑用原先的quartz,那么能不能人这四段代码中抽取出来,因为以xml的形式,一个job的运行,只需要这四段代码。找到了源码包,先找开MethodInvokingJobDetailFactoryBean,关键代码如下:

@Override
	@SuppressWarnings("unchecked")
	public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
		prepare();

		// Use specific name if given, else fall back to bean name.
		String name = (this.name != null ? this.name : this.beanName);

		// Consider the concurrent flag to choose between stateful and stateless job.
		Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);

		// Build JobDetail instance.
		JobDetailImpl jdi = new JobDetailImpl();
		jdi.setName(name);
		jdi.setGroup(this.group);
		jdi.setJobClass((Class) jobClass);
		jdi.setDurability(true);
		jdi.getJobDataMap().put("methodInvoker", this);
		this.jobDetail = jdi;

		postProcessJobDetail(this.jobDetail);
	}
afterPropertiesSet()实现的是InitializingBean中的方法。根据注释,Interface to be implemented by beans that need to react once all their properties have been set by a BeanFactory。实现了这个接口的类,当属性都设置完成,将执行一次。在这里一个JobDetail对象封装完成,postProcessJobDetail没有默认实现,空方法。

CronTriggerFactoryBean也是相同道理:

@Override
	public void afterPropertiesSet() throws ParseException {
                ......

		CronTriggerImpl cti = new CronTriggerImpl();
		cti.setName(this.name);
		cti.setGroup(this.group);
		cti.setJobKey(this.jobDetail.getKey());
		cti.setJobDataMap(this.jobDataMap);
		cti.setStartTime(this.startTime);
		cti.setCronExpression(this.cronExpression);
		cti.setTimeZone(this.timeZone);
		cti.setCalendarName(this.calendarName);
		cti.setPriority(this.priority);
		cti.setMisfireInstruction(this.misfireInstruction);
		cti.setDescription(this.description);
		this.cronTrigger = cti;
	}


最后是SchedulerFactoryBean,代码略。

弄清了思路,基本上是这样子的:

@SuppressWarnings({ "rawtypes", "unchecked" })
	public void startJob(Integer jobId, String jobName, String method,
			String clazz, String cron, String startDelay, String triggerName,
			Properties p) throws Exception
			 {

		if (MessageContainer.quartzMap.containsKey(jobName)) {
			System.out.println("已经有名为" + jobName + "的Job了");
		}else {
			/*
			 * JobDetail
			 */
			MethodInvokingJobDetailFactoryBean methodJD = new MethodInvokingJobDetailFactoryBean();
			methodJD.setName(jobName);

			/*
			 * //根据类名获取Class对象 Class c=Class.forName(clazz); //参数类型数组 Class[]
			 * parameterTypes={Integer.class}; //根据参数类型获取相应的构造函数
			 * java.lang.reflect.Constructor
			 * constructor=c.getConstructor(parameterTypes); //参数数组 Object[]
			 * parameters={jobId}; //根据获取的构造函数和参数,创建实例 Object
			 * o=constructor.newInstance(parameters);
			 */

                        // 数据库中类路径是com.xxx.Test形式的字符串,通过反射获取一个实例。Test中可能使用了@autowire注入了其他对象,
                        // 所以必须要从spring中get出来,不然o里面注入的都是空的
			Class c = Class.forName(clazz);
			Object o = BeanHoldFactory.getApplicationContext().getBean(c);
                        // 这里通过setJobId方法向Test对象中传入了一个值
                        Method mth = c.getMethod("setJobId", Integer.class);
			mth.invoke(o, jobId);

			methodJD.setTargetObject(o);
			methodJD.setTargetMethod(method);
			methodJD.afterPropertiesSet();
			JobDetail jd = methodJD.getObject();

			/*
			 * Trigger
			 */
			CronTriggerFactoryBean crTiger = new CronTriggerFactoryBean();
			crTiger.setCronExpression(cron);
			crTiger.setName(triggerName);
			crTiger.setStartDelay(Integer.valueOf(startDelay));
			crTiger.setJobDetail(jd);
			//crTiger.setMisfireInstruction(Integer.valueOf(misfire));
			crTiger.afterPropertiesSet();
			Trigger trigger = crTiger.getObject();
			
			/*
			 * scheduler 
			 */

                        Properties p = new Properties();
                        p.setProperty("org.quartz.threadPool.threadCount", "1");
                        p.setProperty("org.quartz.scheduler.skipUpdateCheck", "true");

                        SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
			// setTriggers(... triggers)可一次性传入多个trigger
                        scheduler.setTriggers(trigger);
                        scheduler.setQuartzProperties(p);
			scheduler.afterPropertiesSet();

			MessageContainer.quartzMap.put(jobName, scheduler);
			
 			scheduler.start();
		}
	}


可以看出JobDetail,Trigger和Scheduler都是new出来的,一一对应,这是因为在scheduler只有setTrigger,如果在页面新加一个job,相同的代码执行一遍,每set一次,原有的trigger就会丢失,而且线程数不对。用一个HashMap来存储scheduler。这样每次add时,可以通过contains(jobName)来判断是不已经存在。

你可以使用ApplicationListener在spring容器完成时,先把表的数据读到内存中,然后再看有个job,就循环执行多少次。Scheduler也提供了destroy()来销毁整个调度器,这样注册在上面的所有trigger都会消失,线程终结,这是最彻底的方式。

我的表设计,可以参考一下。



总体来说,目前基本满足了要求。既不要一大堆表,又可以数据库配置,不重启应用。这也告诉我一件事,了解一下别人是怎么写代码的是挺有意思的。都是牛人。

如果大家有更好的方法,敬请赐教。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: