java jobkey_JAVA编码(25)——实现Job插件(实现定时调度)

我们在做项目的时候,可能会遇到这样的问题:如何定时或周期性地调用某个类的方法呢?

您丰富的经验或许会告诉自己,定时器(Timer)或调度器(Scheduler)可以实现该功能,当然 JDK 自带的 java.util.Timer 也是一个轻量级选择,但功能比较欠缺,它不能实现一些较复杂的任务调度,比如:在周一至周五,每天8 点到 20 点,每隔 5分钟调用一次。

正因为 Quartz 可以做以上这件看似简单而又复杂的事情,所以它在业界才会流行起来。此外它也能保持着苗条的身材,为我们展现它的骄姿,所以它往往是开发人员实现任务调度的首选。

我们先来看看 Quartz 是怎样使用的吧!1使用 Quartz 实现任务调度

Quartz 告诉我们,所有的 Job 类必须实现 org.quartz.Job 接口,该接口仅提供了一个 execute 方法,该方法会被 Quartz 框架自动调度。

我们先来一个简单的 Quartz Job 吧!

public class QuartzHelloJob implementsJob {private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

@Overridepublic void execute(JobExecutionContext jobExecutionContext) throwsJobExecutionException {

System.out.println(format.format(new Date()) + " Hello Quartz!");

}

}

我们需要每隔一段时间输出一个 Hello Quartz 的文本信息,需要自定义一个 Job 类。在 Job 类中必须实现 Job 接口,填充它的 execute 方法。

需要说明的是,该方法中有个 JobExecutionContext 参数,它表示 Job 执行上下文,它是一个很牛逼对象,在一些复杂的场景下会使用该参数,比如实现数据传递功能,现在暂时忽略它吧。这个方法还要求我们,必须在 execute 方法的声明处,定义可抛出 JobExecutionException 异常,否则 Job 的调用者无法捕获到 Job 类中产生的任何异常。

下面,我们需要借助 Quartz 提供的几个核心组件来完成任务调度功能,不妨编写一个单元测试来实现着一切吧!

public classQuartzJobTest {

@Testpublic voidtest() {try{

JobDetail jobDetail= JobBuilder.newJob(QuartzHelloJob.class).build();

ScheduleBuilder builder= CronScheduleBuilder.cronSchedule("0/1 * * * * ?");

Trigger trigger=TriggerBuilder.newTrigger().withSchedule(builder).build();

Scheduler scheduler=StdSchedulerFactory.getDefaultScheduler();

scheduler.scheduleJob(jobDetail, trigger);

scheduler.start();

sleep(3000);

scheduler.shutdown(true);

sleep(3000);

}catch(SchedulerException se) {

se.printStackTrace();

}

}private void sleep(longms) {try{

Thread.sleep(ms);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

我们需要根据刚才编写的 QuartzHelloJob 类来创建一个 JobDetail 对象。

需要指定一个 cron 表达式“0/1 * * * * ?”,它表示每隔 1秒钟的意思,进而创建一个 ScheduleBuilder 对象。

根据 ScheduleBuilder 对象我们来创建一个 Trigger 对象。

创建一个 Scheduler 对象,并将 JobDetail 对象与 Trigger 对象加入其中,这样才能开启这个调度。

不妨延迟3秒,看看控制台的输出。

关闭当前的调度,允许在结束调度之前等待最后一个 Job 运行结束(防止该 Job 没有执行完就被扼杀了)。

最后再延迟3秒,看看控制台还会不会输出相关信息。

您没有看错,要使用 Quartz,你就需要知道这些核心对象(组件)到底是干嘛的,它们主要包括:JobDetail、ScheduleBuilder、Trigger、Scheduler 等。

这一切似乎简单,而又非常繁琐,能否让 Job 更加简化呢?

牛逼的 Spring 提供了一个极简的抽象,我们再来看看如何通过 Spring 来完成任务调度。2使用 Spring 简化任务调度

首先需要告诉 Spring:我们想使用您的任务调度功能。此时必须通过一个简单的配置才行:

需要说明的是,我们采用的是 Spring3 提供的最简单的任务调度解决方案,以前或许您会做大量的 XML 配置,但从 Spring 3以后,推荐我们使用基于注解的方式,而不是 XML 配置方式。

我们可以像这样来定义一个 Job:@Componentpublic classSpringHelloJob {private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

@Scheduled(corn= "0/1 * * * * ?")public voidexecute() {

System.out.println(format.format(new Date()) + " Hello Spring!");

}

}

这里就可以看到明显的简化,无需实现所谓的 Job 接口,但必须提供一个可以被调度的方法,方法名叫什么无所谓,为了理解上保持一致,不妨命名为 execute 吧。

更有特色的是,Spring 使用了一个名为 @Scheduled 的注解,可定义一个 cron 表达式,从而指定方法的调用方式。

需要补充说明的是,Spring 提供了一个 @Component 注解,来声明该对象是由 Spring IOC 容器来管理的,也就是说,这样声明后,我们就可以使用“依赖注入”功能了。

其实,在上面这个 Job 类中可定义多个 execute 方法,但都需要使用各自的 @Scheduled 来定义调度策略。但我个人认为,一个 Job 类只提供一个 execute 方法比较合适,这就是传说中的“单一指责原则”了!

看来 Spring 确实够强悍的,轻松几下,就能实现 Quartz 较为复杂的代码结构。

机灵的 Smart 也不甘示弱,也想提供一个与 Spring 相似的任务调度框架。那么,我们应该如何实现呢?3开发 Smart Job 插件3.1编写一个 Job 类

目前,Smart 的插件已经很多了,缺少了 Job 插件似乎有些遗憾,所以我们无论如何都要提供一个任务调度框架,才对得起 Smart 的精神:Smart your dev,Smart your life!

既然 cron 表达式已经这么强大了,我们不妨就使用它来作为 Smart Job 插件的调度公式吧,我们可以这样来写一个 Smart Job:@Bean

@Job("0/1 * * * * ?")public class SmartHelloJob extendsBaseJob {private static final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");

@Overridepublic voidexecute() {

System.out.println(format.format(new Date()) + " - Hello Smart!");

}

}

上面定义了一个 Job 类,根据我们一贯的作风,该 Job 类继承一个 BaseJob 抽象类,必须完成该抽象父类的 execute 方法才行,可以肯定的是,该方法就是需要调度执行的方法。

当初设想,其实也可以不继承任何的类,就像 Spring 一样,但对于一个 Job 而言,继承一个 BaseJob,将来或许能够提供一些通用的功能,也方便我们扩展。至少现在我们得满足 Quartz 的要求:每个 Job 类必须实现 org.quartz.Job 接口。所以 BaseJob 应该这样写:

public abstract class BaseJob implementsJob {private static final Logger logger = Logger.getLogger(BaseJob.class);

@Overridepublic final void execute(JobExecutionContext context) throwsJobExecutionException {try{

execute();

}catch(Exception e) {

logger.error("执行 Job 出错!", e);

}

}public abstract voidexecute();

}

在 BaseJob 中,我们屏蔽了 JobExecutionContext 与 JobExecutionException。以上代码可以看出,这里使用了模板方法模式,BaseJob 的子类必须实现自己定义的 execute 方法。

此外,需要注意的是,与 Spring 不同,Smart 提供了一个 @Job 注解,该注解类似于 Spring 的 @Scheduled 注解,但它是标注在 Job 类上的,而不是 execute 方法上。这样也保证了一个 Job 类对应一个业务逻辑,不会将多个业务逻辑混入到同一个 Job 类中,这是为了“单一指责原则”而故意这样设计的。

需要补充的是,Smart 的 @Bean 注解就相当于 Spring 的 @Component 注解。

需要像 Spring 那样再做一个 XML 配置吗?——不需要了。3.2提供 JobHelper 类

为了封装 Quartz 繁琐的 API,我们需要编写一个 JobHelper,它是这样写的:

public classJobHelper {private static final Logger logger = Logger.getLogger(JobHelper.class);private static final Map, Scheduler> jobMap = new HashMap, Scheduler>();private static final JobFactory jobFactory = newSmartJobFactory();public static void startJob(Class>jobClass, String cron) {try{

Scheduler scheduler=createScheduler(jobClass, cron);

scheduler.start();

jobMap.put(jobClass, scheduler);if(logger.isDebugEnabled()) {

logger.debug("[Smart] start job: " +jobClass.getName());

}

}catch(SchedulerException e) {

logger.error("启动 Job 出错!", e);

}

}public static voidstartJobAll() {

List> jobClassList = ClassHelper.getClassListBySuper(BaseJob.class);if(CollectionUtil.isNotEmpty(jobClassList)) {for (Class>jobClass : jobClassList) {if (jobClass.isAnnotationPresent(Job.class)) {

String cron= jobClass.getAnnotation(Job.class).value();

startJob(jobClass, cron);

}

}

}

}public static void stopJob(Class>jobClass) {try{

Scheduler scheduler=getScheduler(jobClass);

scheduler.shutdown(true);

jobMap.remove(jobClass);//从 jobMap 中移除该 Job

if(logger.isDebugEnabled()) {

logger.debug("[Smart] stop job: " +jobClass.getName());

}

}catch(SchedulerException e) {

logger.error("停止 Job 出错!", e);

}

}public static voidstopJobAll() {for (Class>jobClass : jobMap.keySet()) {

stopJob(jobClass);

}

}public static void pauseJob(Class>jobClass) {try{

Scheduler scheduler=getScheduler(jobClass);

scheduler.pauseJob(newJobKey(jobClass.getName()));if(logger.isDebugEnabled()) {

logger.debug("[Smart] pause job: " +jobClass.getName());

}

}catch(SchedulerException e) {

logger.error("暂停 Job 出错!", e);

}

}public static void resumeJob(Class>jobClass) {try{

Scheduler scheduler=getScheduler(jobClass);

scheduler.resumeJob(newJobKey(jobClass.getName()));if(logger.isDebugEnabled()) {

logger.debug("[Smart] resume job: " +jobClass.getName());

}

}catch(SchedulerException e) {

logger.error("恢复 Job 出错!", e);

}

}private static Scheduler createScheduler(Class>jobClass, String cron) {

Scheduler scheduler= null;try{

@SuppressWarnings("unchecked")

JobDetail jobDetail= JobBuilder.newJob((Class extends org.quartz.Job>) jobClass)

.withIdentity(jobClass.getName())

.build();

Trigger trigger=TriggerBuilder.newTrigger()

.withIdentity(jobClass.getName())

.withSchedule(CronScheduleBuilder.cronSchedule(cron))

.build();

scheduler=StdSchedulerFactory.getDefaultScheduler();

scheduler.setJobFactory(jobFactory);//从 Smart IOC 容器中获取 Job 实例

scheduler.scheduleJob(jobDetail, trigger);

}catch(SchedulerException e) {

logger.error("创建 Scheduler 出错!", e);

}returnscheduler;

}private static Scheduler getScheduler(Class>jobClass) {

Scheduler scheduler= null;if(jobMap.containsKey(jobClass)) {

scheduler=jobMap.get(jobClass);

}returnscheduler;

}

}

代码稍微有点长,但是首先需要明确的是,JobHelper 是封装 Quartz 常用 API 的。此外,在里面还有一个重要的 Map 对象:

Map, Scheduler>jobMap

它的 key 就是 Job 类的 Class 对象,value 是 Quartz 的 Scheduler 对象。也就是说,一个 Job 对象对应一个 Scheduler 对象,每个 Job 都有各自的 Scheduler,而并非所有的 Job 都公用同一个 Scheduler。

下面的几个方法无非就是:启动 Job、启动所有 Job、关闭 Job、关闭所有 Job、暂停 Job、恢复 Job,还有几个私有方法。

在 startJob 方法中,我们需要从 Smart IOC 容器中获取 Job 实例,所以要自定义一个 JobFactory,这样的扩展机制也是 Quartz 给我们的礼物。

下面就是我们自定义的 SmartJobFactory:

public class SmartJobFactory implementsJobFactory {

@Overridepublic Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throwsSchedulerException {

JobDetail jobDetail=bundle.getJobDetail();

Class extends Job> jobClass =jobDetail.getJobClass();returnBeanHelper.getBean(jobClass);

}

}

似乎一切都是那么自然,那么简单,好像 Quartz 就是为 Smart 准备的一样。我们直接从 TriggerFiredBundle 中获取 JobDetail,根据 JobDetail 获取 Job 类,根据 Job 类从 BeanHelper 中获取 Bean 的对象实例。

为什么要从 Smart IOC 容器中获取 Bean 实例?因为如果不这样做的话,我们的 Job 类就由 Quartz 来管理了(它是通过反射来创建的实例的),那么我们也无法在 Job 类中使用 @Inject 注解来注入我们所需要的对象了。

OK,到这里我想已经大致说清楚了,JobHelper 的实现原理,但是如何让 Job 类中的 @Job 注解生效呢?这个就要用到 Smart 的插件机制了。3.3实现 JobPlugin 类

正如我们所知,Job 不但需要启动,还需要停止。实现 Job 的启动应该比较简单,就是 Smart Plugin 接口的 init 方法了。但 Job 的停止应该如何实现呢?

在回答这个问题之前,我们先回答有些朋友可能会提出的质疑:为什么要停止?Web 服务器(如 Tomcat)停止了,Job 不会自动停止吗?

实际情况或许会超乎您的想象,即使 Tomcat 停止了,Job 还仍然活着!它就像幽灵一样,匪夷所思。从技术的角度上来解释,其实 Job 就是一个 daemon 方式的 Thread 而已。我想您已经知道了缘由了。

那么,我们就需要提供一个插件的销毁机制,此时就需要考虑到“开闭原则”了,我们稍微扩展一下即可实现。

以下是改进后的 Plugin 接口:

public interfacePlugin {voidinit();voiddestroy();

}

可见,该接口提供了一个 destroy 方法而已,那么它的实现类都必须实现这两个方法。或许对于某些插件而言,destroy 方法是多余的,但空实现或许也是一种不错的选择。

对于 Job 插件而言,destroy 方法就非常重要了,因为我们需要在其中停止所有的 Job 调度。

其实 JobPlugin 真的很简单:

public class JobPlugin implementsPlugin {

@Overridepublic voidinit() {

JobHelper.startJobAll();

}

@Overridepublic voiddestroy() {

JobHelper.stopJobAll();

}

}

应该无需做任何解释了,因为它真的很简单。

细心的您肯定会注意到:init 方法可以在 Smart 框架加载的时候被调用,但 destroy 方法又在哪里调用呢?

我们不妨回头去看看 Plugin 的 init 方法是如何被调用的吧。

在 Smart 框架中,有一个 ContainerListener。没错!它就是一个 Listener,更确切地说,它应该是一个 ServletContextListener,它可以监听 Web 容器的初始化与销毁实现,也就是说,当 Tomcat 启动时与停止时,它都可以察觉到这些事件,这不就是“观察者模式”的最佳实践吗?@WebListenerpublic class ContainerListener implementsServletContextListener {

@Overridepublic voidcontextInitialized(ServletContextEvent sce) {//初始化相关 Helper 类

Smart.init();//添加 Servlet 映射

addServletMapping(sce.getServletContext());

}

@Overridepublic voidcontextDestroyed(ServletContextEvent sce) {

}

...

当 Tomcat 初始化时会调用 contextInitialized 方法;当 Tomcat 停止时会调用 contextDestroyed 方法。此时,我们应该找到了停止 Job 的最佳地点了。

需要补充说明的是,我们目前是在 Smart.init() 方法内部来调用 Plugin 的 init 方法的,这是一个多态调用方式。有兴趣的朋友,可以阅读一下 Smart 框架的源码。

为了将 PluginHelper 打造成一款强大的武器,我们需要从它那里获取所有的 Plugin,这样才能遍历这些 Plugin,从而通过多态的方式调用每个 Plugin 实现类的 destroy 方法。

现在的 ContainerListener 看起来应该更加的丰满了!@WebListenerpublic class ContainerListener implementsServletContextListener {

@Overridepublic voidcontextInitialized(ServletContextEvent sce) {//初始化相关 Helper 类

Smart.init();//添加 Servlet 映射

addServletMapping(sce.getServletContext());

}

@Overridepublic voidcontextDestroyed(ServletContextEvent sce) {//销毁插件

destroyPlugin();

}

...public static voiddestroyPlugin() {

List pluginList =PluginHelper.getPluginList();for(Plugin plugin : pluginList) {

plugin.destroy();

}

}

...

下面是扩展后的 PluginHelper:

public classPluginHelper {private static final Logger logger = Logger.getLogger(PluginHelper.class);//创建一个 Plugin 列表(用于存放 Plugin 实例)

private static final List pluginList = new ArrayList();static{try{//获取并遍历所有的 Plugin 类(实现了 Plugin 接口的类)

List> pluginClassList = ClassUtil.getClassListBySuper(FrameworkConstant.PLUGIN_PACKAGE, Plugin.class);for (Class>pluginClass : pluginClassList) {//创建 Plugin 实例

Plugin plugin =(Plugin) pluginClass.newInstance();//调用初始化方法

plugin.init();//将 Plugin 实例添加到 Plugin 列表

pluginList.add(plugin);

}

}catch(Exception e) {

logger.error("初始化 PluginHelper 出错!", e);

}

}public static ListgetPluginList() {returnpluginList;

}

}

直到今天,Smart 的插件机制看起来终于有那么一点样子了,Plugin 接口管理了每个插件的生命周期,主要包括:init(出生)与 destroy(死亡)。

通过扩展 Smart 的插件机制,我们的 Job 插件也就初步实现了!

最后想与大家分享的是,用过 Spring 任务调度的朋友,是否想过一个问题:

能否不要在 Tomcat 启动的时自动创建 Job 呢?我们其实是想通过代码的方式,控制 Job 的启动、停止、暂停、恢复,更加灵活地控制 Job 的这些行为。

可惜目前的 Spring 尚不支持以上这个特性,我们只能通过 Quartz API 来实现了。看来 Spring 虽然美丽,但并不完美。

如果您有了 Smart,情况就会完全不一样。我们可以定义一个 Job 类,此时不要定义 @Job 注解,那么该 Job 是不会自动启动的。随后我们可以通过 JobHelper 的 startJob 方法随时启动 Job,通过 stopJob 方法随时停止 Job,此外还有 pauseJob 与 resumeJob 方法,用来暂停 Job 与恢复 Job,这些都不再是梦想。因为 JobHelper 就是 Quartz 的一个简单封装,还有哪些功能非常实用,将来都可以在 JobHelper 类中进行扩展。

下面这段代码或许会让您今天的心情充满愉悦!

public class SmartJobTest extendsBaseTest {

@Testpublic voidtest() {

JobHelper.startJob(SmartHelloJob.class, "0/1 * * * * ?");

sleep(3000);

JobHelper.stopJob(SmartHelloJob.class);

sleep(3000);

}private void sleep(longms) {try{

Thread.sleep(ms);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

这就是 Smart 对 Quartz 的极简封装!Quartz 所有的一切都隐藏在 JobHelper 背后了,您不妨回头去比较一下 SmartJobTest 与 QuartzJobTest 差异,相信您会和我有同样的感受。

以上便是 Smart Job 插件的实现原理与开发过程,期待您的意见与建议,因为这些反馈会让我学到更多的东西!

Smart Job 插件源码地址:http://git.oschina.net/huangyong/smart-plugin-job

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值