扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
为何需要任务调度?
在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, System.out.println("Initializing Scheduler PlugIn for Jobs!"); // Retrieve the factory from the ServletContext. // Start the scheduler in case, it isn't started yet |
<servlet> <servlet-name>QuartzInitializer</servlet-name> <servlet-class> <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); |
实际工作在何处?
至此我们已决定Trigger,可以开始调度工作了。看上去一切都已完成,但实际上我们只是调度了一项任务,还有最重要的一步有待完成。注意HelloWorld.class作为参数传递给了JobDetail的构造函数。这个类就是实际完成工作的地方。HelloWorld继承了Quartz的Job类,并覆盖了execute()方法。当任务管理器决定运行这个任务时,execute()方法将被调用。来看代码:
import org.quartz.JobDataMap; //extend the proper Quartz class //override the execute method // Every job has it's own job detail |
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者