分享

Quartz中扩展MethodInvokingJobDetailFactoryBean实现...

 -ー意孤行ノ 2009-03-19

利用Quartz来实现对任务的调度已经被广泛地应用了,一个利用Quartz来进行任务调度的典型配置如下:

  1. <bean id="testTask" class="com.alisoft.xx.TestTask" />   
  2. <bean id="xxJobDetail"    
  3.     class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">   
  4.     <property name="concurrent">   
  5.         <value>false</value>   
  6.     </property>   
  7.     <property name="targetObject">   
  8.         <ref bean="testTask"/>   
  9.     </property>   
  10.     <property name="targetMethod">   
  11.         <value>execute</value>   
  12.     </property>   
  13. </bean>   
  14. <!-- SimpleTriggerBean可以用org.springframework.scheduling.quartz.CronTriggerBean代替 -->   
  15. <bean id="xxTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerBean">   
  16.     <property name="jobDetail">   
  17.         <ref bean="xxJobDetail"/>   
  18.     </property>   
  19.     <property name="startDelay">   
  20.         <value>1000</value>   
  21.     </property>   
  22.     <property name="repeatInterval">   
  23.         <value>60000</value>   
  24.     </property>   
  25. </bean>   
  26. <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">   
  27.     <property name="triggers">   
  28.         <list>   
  29.             <ref local="xxTriggerBean"/>   
  30.         </list>   
  31.     </property>   
  32. </bean>  

 

若有多个任务需要调度,则配置多个JobDetail、Trigger即可,待执行的Task bean没有啥任何要求,不需要extends任何class、或者implements任何interface,只是被执行的method需要是无参数的;

但是在我们现实项目中,仅仅完成这样的功能是还不够的,我们还需要争取完成以下几点:

  1. 对所有的Task实现执行开始、结束时间的记录;
  2. 对每天的Task执行结果持久化、或者通过email、旺旺消息等方式告知相关owner;
  3. 当我们有多台Task服务器时,如何保证每个任务只在一台服务器上执行;

针对以上几个问题,我们也都有很简单、直接的办法可以搞定,比如说1、2两点可以直接在每个任务中各自编码完成,但是这样不仅每个task都要写非常类似的重复代码、而且不能保证每个任务的执行情况都被记录,因为某些task的编码人员就不会在意这些非功能性需求;对于第3点,我们也可以通过配置来完成向不同task服务器部署不一样的发布包来完成,但这给发布带来了麻烦,这意味着有多少台task服务器,就需要通过修改配置重新打包并发布多少次;

其实我们可以利用Spring默认提供的AOP来非常优雅的解决这几个问题:扩展MethodInvokingJobDetailFactoryBean来实现对任务调度时的拦截!其关键代码为MethodInvokingJobDetailFactoryBean.MethodInvokingJob.executeInternal(JobExecutionContext context);这个method中;

具体步骤如下:

  1. 新增所有Task都需要实现的interface,可参考如下代码:
    1. public interface TaskHandler {   
    2.     /**  
    3.      * 任务调度执行需要实现的method  
    4.      */  
    5.     TaskResult execute();   
    6. }  
  2. 基于MethodInvokingJobDetailFactoryBean实现自定义的JobDetailFactoryBean,在具体执行待调度任务的method前后加入公用逻辑,比如记录开始、结束日期、判断该task是否由该台服务器执行、任务执行完成之后将运行结果进行持久化或者发email等给相关owner;可参考如下代码:
    1. public class AppsMethodInvokingJobDetailFactoryBean extends  
    2.         MethodInvokingJobDetailFactoryBean {   
    3.     protected static final Log logger = LogFactory.getLog(AppsMethodInvokingJobDetailFactoryBean.class);   
    4.        
    5.     /* (non-Javadoc)  
    6.      * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean#afterPropertiesSet()  
    7.      */  
    8.     @Override  
    9.     public void afterPropertiesSet() throws ClassNotFoundException,   
    10.             NoSuchMethodException {   
    11.         super.afterPropertiesSet();   
    12.            
    13.         logger.info("origin jobClass : " + ((JobDetail)super.getObject()).getJobClass().getName());   
    14.         // Consider the concurrent flag to choose between stateful and stateless job.   
    15.         if(MethodInvokingJob.class.getName().equals(((JobDetail)super.getObject()).getJobClass().getName())) {   
    16.             ((JobDetail)super.getObject()).setJobClass(AppsMethodInvokingJob.class);   
    17.         } else {   
    18.             ((JobDetail)super.getObject()).setJobClass(AppsStatefulMethodInvokingJob.class);   
    19.         }   
    20.         logger.info("new jobClass : " + ((JobDetail)super.getObject()).getJobClass().getName());   
    21.     }   
    22.   
    23.     public static class AppsMethodInvokingJob extends MethodInvokingJob {   
    24.         protected static final Log logger = LogFactory.getLog(AppsMethodInvokingJob.class);   
    25.         private MethodInvoker methodInvoker;   
    26.         private String errorMessage;   
    27.   
    28.         /**  
    29.          * Set the MethodInvoker to use.  
    30.          */  
    31.         public void setMethodInvoker(MethodInvoker methodInvoker) {   
    32.             this.methodInvoker = methodInvoker;   
    33.             this.errorMessage = "Could not invoke method '" + this.methodInvoker.getTargetMethod() +   
    34.                     "' on target object [" + this.methodInvoker.getTargetObject() + "]";   
    35.         }   
    36.   
    37.         /**  
    38.          * Invoke the method via the MethodInvoker.  
    39.          */  
    40.         protected void executeInternal(JobExecutionContext context) throws JobExecutionException {   
    41.             Date startDate = new Date();   
    42.             String taskName = methodInvoker.getTargetClass().getName();   
    43.             TaskResult taskResult;   
    44.             try {   
    45.                 if (logger.isInfoEnabled()) {   
    46.                     logger.info(taskName + " job start at " + startDate);   
    47.                 }   
    48.                    
    49.                 //根据当前服务器主机名或者IP判断是否需要执行该任务   
    50.                 //TODO Code   
    51.                    
    52.                 //调用具体task执行method代码   
    53.                 taskResult = this.methodInvoker.invoke();                  
    54.             } catch (Exception ex) {   
    55.                 logger.error(taskName + " accounted a error : " + this.errorMessage, ex);   
    56.                 throw new JobExecutionException(this.errorMessage, ex, false);   
    57.             } finally {   
    58.                 if(logger.isInfoEnabled()) {   
    59.                     logger.info(taskName + " job end   at " + new Date());   
    60.                 }   
    61.                    
    62.                 //将task执行结果taskResult进行持久化、或者通知相关owner   
    63.                 //TODO Code   
    64.             }   
    65.         }   
    66.     }   
    67.   
    68.     public static class AppsStatefulMethodInvokingJob extends AppsMethodInvokingJob {   
    69.     }   
    70. }  
  3. 将自定义JobDetailFactoryBean的bean配置设置为abstract,从而减少每个task的相关配置量,新的代码可参考如下配置:
    1. <bean id="appsAbstractJobDetail" class="com.alisoft...AppsMethodInvokingJobDetailFactoryBean"  
    2.     abstract="true">   
    3.     <property name="concurrent" value="false" />   
    4.     <property name="targetMethod" value="execute" />   
    5. </bean>   
    6. <bean id="testTaskHandler" class="com.alisoft...task.TestTaskHandler" />   
    7. <bean id="testTaskJobDetail" parent="appsAbstractJobDetail">         
    8.     <property name="targetObject" ref="testTaskHandler" />   
    9. </bean>   
    10. <bean id="testTaskTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">   
    11.     <property name="jobDetail" ref="testTaskJobDetail" />   
    12.     <property name="cronExpression" value="0 10 0 * * ?" />   
    13. </bean>  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多