服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java web应用中任务调度解析(1)

任务调度是大型j2ee web应用中常见的工作。开发者希望以指定的间隔时间执行各类操作,并完成一些无需用户输入的任务。

java中可有无数方法来做到这一点,但是在web应用中却并没有这方面的统一标准。当许多开发人员参与同一个项目,并且以各自不同的方式来实现任务调度时,就可能产生很大问题。内存和同步问题就是必须首先考虑的两件事。事实上,一些开发者试图调用操作系统层面的任务调度机制,如unix平台上的cron。这种编程实践也许并不是太坏,但它将直接导致可移植性被抛到九霄云外。

为何需要任务调度?

在web应用中,大多数任务是以一种"防止用户长时间等待"的方式完成的。在google搜索这样的例子中,减少等待时间对用户体验来说至关重要。异步任务的一种解决方案是在用户提交后生成一个线程(来处理异步任务),但这也不能解决那些需要以一定时间间隔重复运行任务、或在每天的指定时间运行任务的情况。

让我们从一个数据库报表的例子来看看任务调度能如何帮助改善系统设计。报表可能是错综复杂的,这取决于用户所需数据的种类,以及是否需要从一个或多个数据库收集大量数据。用户可能需要很长时间来运行这样的"按需"报表。因此,我们向这个报表示例中添加任务调度机制,以便用户可以安排在任何他们需要的时间生成报表,并以pdf或其他格式在email中发送。用户可以让报表在每天的凌晨2:22,系统正处于低负荷时运行;也可以选择只在特定时间运行一次。通过在报表应用中加入任务调度,我们可以为产品添加一项有用的功能,并改善用户体验。

幸运的是,有一个强大的开源解决方案可以让我们以标准的方式在web应用(或任何java应用)中实施任务调度。以下示例展示了在web应用中,如何使用quartz来创建一个任务调度框架。这个示例还使用了struts action framework 插件,以便在web应用启动时初始化任务调度机制。struts是最常见的mvc框架,为大多数开发人员所熟悉。当然除此之外还有许多框架可以协助在web应用中实现mvc模式。

启动时初始化任务调度器

我们首先要做的是建立一个struts插件,让它在容器启动时创建我们的任务调度器。在以下例子中,我们选择tomcat作为web应用容器,不过这些示例在其他容器中也应当可以运行。我们要创建一个struts插件类,并在struts-config.xml中加入几行代码以使之可以工作。

这个插件有两个可配置的初始化参数:startonload指定是否要在容器启动时立即启动任务调度器,而 startupdelay指定启动任务调度器之前的等待时间。启动延时很有用,因为我们可能需要首先执行一些更重要的初始化步骤。此外还可以使用listener机制,以更复杂的方式来通知schedulerplugin何时启动quartz scheduler。

<plug-in classname="schedulerplugin">

<set-property property="startonload" value="false" />

<set-property property="startupdelay" value="0" />

</plug-in>

我们要创建的是一个实现struts插件接口org.apache.struts.action.plugin的单子类schedulerplugin。struts会按照配置文件中出现的顺序初始化各个插件。要特别注意的是init()方法中的代码,在此我们初始化了所需的quartz对象,并得到scheduler。我们的任务信息就要提交到此org.quartz.scheduler对象,后者将在随后讨论。scheduler对象由quartz servlet根据其配置初始化,就像struts初始化它的actionservlet类一样。让我们来看init()方法:

public void init(actionservlet actionservlet,

moduleconfig moduleconfig) {

system.out.println("initializing scheduler plugin for jobs!");

// retrieve the servletcontext

// 获取servletcontext

servletcontext ctx = actionservlet.getservletcontext();

// the quartz scheduler

// quartz scheduler对象

scheduler scheduler = null;

// retrieve the factory from the servletcontext.

// it will be put there by the quartz servlet

// 从servletcontext取得由quartz servlet放置在此的factory对象。

stdschedulerfactory factory = (stdschedulerfactory)

ctx.getattribute(quartzinitializerservlet.quartz_factory_key);

try{

// retrieve the scheduler from the factory

// 从factory取得scheduler

scheduler = factory.getscheduler();

// start the scheduler in case, it isn't started yet

// 如果scheduler尚未启动,则启动它

if (m_startonload != null &&

m_startonload.equals(boolean.true.tostring())){

system.out.println("scheduler will start in " +

m_startupdelaystring + " milliseconds!");

//wait the specified amount of time before

// starting the process.

// 在启动之前等待指定长度的时间

thread delayedscheduler =

new thread(new delayedschedulerstarted (

scheduler, m_startupdelay));

//give the scheduler a name. all good code needs a name

//给任务调度器命名。好的代码总该有名字!

delayedscheduler.setname("delayed_scheduler");

//start out scheduler

//启动任务调度器

delayedscheduler.start();

}

} catch (exception e){

e.printstacktrace();

}

sm_scheduler = scheduler;

}

配置过程的第二步是在web.xml中加入用来初始化quartz servlet(org.quartz.ee.servlet.quartzinitializerservlet)的内容,因为需要它将schedulerfactory添加到servletcontext中,以便在我们的struts插件中可以访问。schedulerfactory就是我们在struts插件中获得scheduler对象的来源。除了struts-config.xml 和web.xml之外,还要在web应用的classes目录下放置一个quartz.properties文件。此文件的位置也可以在web.xml中作为quartzinitializerservlet的启动参数来指定。

<servlet>

<servlet-name>quartzinitializer</servlet-name>

<display-name>quartz initializer servlet</display-name>

<servlet-class>

org.quartz.ee.servlet.quartzinitializerservlet

</servlet-class>

<load-on-startup>1</load-on-startup>

<init-param>

<param-name>shutdown-on-unload</param-name>

<param-value>true</param-value>

</init-param>

<init-param>

<param-name>start-scheduler-on-load</param-name>

<param-value>false</param-value>

</init-param>

</servlet>

这里其实完全可以不使用struts和schedulerplugin,但如果将来决定要以其它的任务调度框架替换quartz的话,额外的抽象层就很有用了。长远看来,让一切保持松散耦合总会使工作变得容易些。如果你使用其它mvc框架,也可以用schedulerplugin.init()方法中的代码达到同样的效果。此外,还可以用servlet 2.3规范中的servletcontextlistener来实现同样的初始化过程。

到此为止web应用已配置完毕,我们可以创建一个.war文件并部署到服务器上,从控制台观察schedulerplugin的输出信息。然而在此之前,让我们先看看如何向任务调度器提交一项任务。

我们可以从web应用中的任何类访问schedulerplugin的唯一实例,并调度一些要执行的工作。首先需要一个trigger(触发器)对象来告诉任务何时运行、每隔多久运行一次。quartz支持多种触发器,在这个例子中我们使用crontrigger。

trigger trigger = new crontrigger("trigger1", "group1");

trigger.setcronexpression("0 0 15 ? * wed");

以上的触发器会在每周三的下午3点执行指定任务。现在我们只要创建一个jobdetail对象,并把它和上面的触发器一起传递给schedulerplugin的schedulework()方法。

jobdetail jobdetail =

new jobdetail("hello world job",

"hello world group",

helloworld.class,

true, true, true);

//schedule the work

//调度这项任务

schedulerplugin.schedulework(scheduledjobdetail, trigger);

任务调度是大型j2ee web应用中常见的工作。开发者希望以指定的间隔时间执行各类操作,并完成一些无需用户输入的任务。

java中可有无数方法来做到这一点,但是在web应用中却并没有这方面的统一标准。当许多开发人员参与同一个项目,并且以各自不同的方式来实现任务调度时,就可能产生很大问题。内存和同步问题就是必须首先考虑的两件事。事实上,一些开发者试图调用操作系统层面的任务调度机制,如unix平台上的cron。这种编程实践也许并不是太坏,但它将直接导致可移植性被抛到九霄云外。

为何需要任务调度?

在web应用中,大多数任务是以一种"防止用户长时间等待"的方式完成的。在google搜索这样的例子中,减少等待时间对用户体验来说至关重要。异步任务的一种解决方案是在用户提交后生成一个线程(来处理异步任务),但这也不能解决那些需要以一定时间间隔重复运行任务、或在每天的指定时间运行任务的情况。

让我们从一个数据库报表的例子来看看任务调度能如何帮助改善系统设计。报表可能是错综复杂的,这取决于用户所需数据的种类,以及是否需要从一个或多个数据库收集大量数据。用户可能需要很长时间来运行这样的"按需"报表。因此,我们向这个报表示例中添加任务调度机制,以便用户可以安排在任何他们需要的时间生成报表,并以pdf或其他格式在email中发送。用户可以让报表在每天的凌晨2:22,系统正处于低负荷时运行;也可以选择只在特定时间运行一次。通过在报表应用中加入任务调度,我们可以为产品添加一项有用的功能,并改善用户体验。

幸运的是,有一个强大的开源解决方案可以让我们以标准的方式在web应用(或任何java应用)中实施任务调度。以下示例展示了在web应用中,如何使用quartz来创建一个任务调度框架。这个示例还使用了struts action framework 插件,以便在web应用启动时初始化任务调度机制。struts是最常见的mvc框架,为大多数开发人员所熟悉。当然除此之外还有许多框架可以协助在web应用中实现mvc模式。

启动时初始化任务调度器

我们首先要做的是建立一个struts插件,让它在容器启动时创建我们的任务调度器。在以下例子中,我们选择tomcat作为web应用容器,不过这些示例在其他容器中也应当可以运行。我们要创建一个struts插件类,并在struts-config.xml中加入几行代码以使之可以工作。

这个插件有两个可配置的初始化参数:startonload指定是否要在容器启动时立即启动任务调度器,而 startupdelay指定启动任务调度器之前的等待时间。启动延时很有用,因为我们可能需要首先执行一些更重要的初始化步骤。此外还可以使用listener机制,以更复杂的方式来通知schedulerplugin何时启动quartz scheduler。

<plug-in classname="schedulerplugin">

<set-property property="startonload" value="false" />

<set-property property="startupdelay" value="0" />

</plug-in>

我们要创建的是一个实现struts插件接口org.apache.struts.action.plugin的单子类schedulerplugin。struts会按照配置文件中出现的顺序初始化各个插件。要特别注意的是init()方法中的代码,在此我们初始化了所需的quartz对象,并得到scheduler。我们的任务信息就要提交到此org.quartz.scheduler对象,后者将在随后讨论。scheduler对象由quartz servlet根据其配置初始化,就像struts初始化它的actionservlet类一样。让我们来看init()方法:

public void init(actionservlet actionservlet,

moduleconfig moduleconfig) {

system.out.println("initializing scheduler plugin for jobs!");

// retrieve the servletcontext

// 获取servletcontext

servletcontext ctx = actionservlet.getservletcontext();

// the quartz scheduler

// quartz scheduler对象

scheduler scheduler = null;

// retrieve the factory from the servletcontext.

// it will be put there by the quartz servlet

// 从servletcontext取得由quartz servlet放置在此的factory对象。

stdschedulerfactory factory = (stdschedulerfactory)

ctx.getattribute(quartzinitializerservlet.quartz_factory_key);

try{

// retrieve the scheduler from the factory

// 从factory取得scheduler

scheduler = factory.getscheduler();

// start the scheduler in case, it isn't started yet

// 如果scheduler尚未启动,则启动它

if (m_startonload != null &&

m_startonload.equals(boolean.true.tostring())){

system.out.println("scheduler will start in " +

m_startupdelaystring + " milliseconds!");

//wait the specified amount of time before

// starting the process.

// 在启动之前等待指定长度的时间

thread delayedscheduler =

new thread(new delayedschedulerstarted (

scheduler, m_startupdelay));

//give the scheduler a name. all good code needs a name

//给任务调度器命名。好的代码总该有名字!

delayedscheduler.setname("delayed_scheduler");

//start out scheduler

//启动任务调度器

delayedscheduler.start();

}

} catch (exception e){

e.printstacktrace();

}

sm_scheduler = scheduler;

}

配置过程的第二步是在web.xml中加入用来初始化quartz servlet(org.quartz.ee.servlet.quartzinitializerservlet)的内容,因为需要它将schedulerfactory添加到servletcontext中,以便在我们的struts插件中可以访问。schedulerfactory就是我们在struts插件中获得scheduler对象的来源。除了struts-config.xml 和web.xml之外,还要在web应用的classes目录下放置一个quartz.properties文件。此文件的位置也可以在web.xml中作为quartzinitializerservlet的启动参数来指定。

<servlet>

<servlet-name>quartzinitializer</servlet-name>

<display-name>quartz initializer servlet</display-name>

<servlet-class>

org.quartz.ee.servlet.quartzinitializerservlet

</servlet-class>

<load-on-startup>1</load-on-startup>

<init-param>

<param-name>shutdown-on-unload</param-name>

<param-value>true</param-value>

</init-param>

<init-param>

<param-name>start-scheduler-on-load</param-name>

<param-value>false</param-value>

</init-param>

</servlet>

这里其实完全可以不使用struts和schedulerplugin,但如果将来决定要以其它的任务调度框架替换quartz的话,额外的抽象层就很有用了。长远看来,让一切保持松散耦合总会使工作变得容易些。如果你使用其它mvc框架,也可以用schedulerplugin.init()方法中的代码达到同样的效果。此外,还可以用servlet 2.3规范中的servletcontextlistener来实现同样的初始化过程。

到此为止web应用已配置完毕,我们可以创建一个.war文件并部署到服务器上,从控制台观察schedulerplugin的输出信息。然而在此之前,让我们先看看如何向任务调度器提交一项任务。

我们可以从web应用中的任何类访问schedulerplugin的唯一实例,并调度一些要执行的工作。首先需要一个trigger(触发器)对象来告诉任务何时运行、每隔多久运行一次。quartz支持多种触发器,在这个例子中我们使用crontrigger。

trigger trigger = new crontrigger("trigger1", "group1");

trigger.setcronexpression("0 0 15 ? * wed");

以上的触发器会在每周三的下午3点执行指定任务。现在我们只要创建一个jobdetail对象,并把它和上面的触发器一起传递给schedulerplugin的schedulework()方法。

jobdetail jobdetail =

new jobdetail("hello world job",

"hello world group",

helloworld.class,

true, true, true);

//schedule the work

//调度这项任务

schedulerplugin.schedulework(scheduledjobdetail, trigger);

扫描关注微信公众号