Quartz的简化(只要一张表,动态启停任务)
2016-01-04 14:07
309 查看
项目中有模块依赖到了Quartz来做定时任务。那天和大师研究了一下午,讲一个使用这个工具的一些收获。
首先,用的不是原先的Quartz,而是与spring整合的。需要用到Spring-Conte-Support-4.2.3.Release.jar,Quartz-2.2.2.jar。使用的方式如下
使用xml文件进行配置,都是“四段式”的配置方法。
1是自己写的来做具体逻辑处理的类
2是JobDetail,MethodInvokingJobDetailFactoryBean是spring对Quartz的JobDetail的包装,在它里面,定义了一个来自org.quartz包的JobDetail。
Interface to be implemented by objects used within a
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 (
the object that it creates.
大意是说一个bean实现了它,通常用来作为一个向外暴露对象的工厂,而非直接得到它本身,向外暴露的对象由泛型指定,通过覆盖的getObject()返回真正的对象。
This interface is heavily used within the framework itself, for example for the AOP
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,关键代码如下:
CronTriggerFactoryBean也是相同道理:
最后是SchedulerFactoryBean,代码略。
弄清了思路,基本上是这样子的:
可以看出JobDetail,Trigger和Scheduler都是new出来的,一一对应,这是因为在scheduler只有setTrigger,如果在页面新加一个job,相同的代码执行一遍,每set一次,原有的trigger就会丢失,而且线程数不对。用一个HashMap来存储scheduler。这样每次add时,可以通过contains(jobName)来判断是不已经存在。
你可以使用ApplicationListener在spring容器完成时,先把表的数据读到内存中,然后再看有个job,就循环执行多少次。Scheduler也提供了destroy()来销毁整个调度器,这样注册在上面的所有trigger都会消失,线程终结,这是最彻底的方式。
我的表设计,可以参考一下。
总体来说,目前基本满足了要求。既不要一大堆表,又可以数据库配置,不重启应用。这也告诉我一件事,了解一下别人是怎么写代码的是挺有意思的。都是牛人。
如果大家有更好的方法,敬请赐教。
首先,用的不是原先的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
BeanFactorywhich 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.ProxyFactoryBeanor 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都会消失,线程终结,这是最彻底的方式。
我的表设计,可以参考一下。
总体来说,目前基本满足了要求。既不要一大堆表,又可以数据库配置,不重启应用。这也告诉我一件事,了解一下别人是怎么写代码的是挺有意思的。都是牛人。
如果大家有更好的方法,敬请赐教。
相关文章推荐
- ViewCompat.animate 动画实现方式
- Linux安装nginx
- CocosBuilder使用记录
- SQL Server性能优化——等待——SLEEP_BPROOL_FLUSH
- emmet 教程 emmet快捷键大全
- Android APK反编译详解(附图)
- springmvc项目搭建过程中遇到的BUG及其解决方法
- str.subString的用法
- 理论: 图论(13): 最大流dinic算法
- c语言中数组中的地址和指针的关系
- ogre
- ExecutorService生命周期
- 手把手,74行代码实现手写数字识别
- 2015年马云熬过的鸡汤:阿里官方公布了这8句话
- nginx源码剖析(1)----概要
- CTE的使用
- Tslib移植与分析【转】
- 断言
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- Linux Shell 编程语法