• Springjob(quartz)任务监控界面(组件)


    俺的第一个文章,有掌声的给掌声,没掌声的给鲜花啦!

    起因:因系统的一个定时任务突然执行不正常了,原来是一个时跑一次,现在偶尔跑,偶尔不跑,日志跟踪二天只跑了一次,这个时间段内没有对系统做任务变更,日志也没有任务异常,用VisualVM远程JMX的方式不能正常监控到进程(待努力重试),因此临时起意想做一下任务监控界面,且形成一个组件,方便管理员查看所有任务列表,及方便调整,暂停等。

    本来参考了网上一些例子,都不适合我的需求,因此自己写了一份。代码主要参考了quartz,spring-job相关官方代码及例子。

    本文提供一种思路,也许你有更好实现,能否回复一下?一起讨论?

    目标:对管理员来说,希望可看到每个任务信息,以及当前状态,历史执行情况及日志,可对当前任务可以暂停,启动,立即执行,查看异常。

    当然,以下数据都是持久在数据库。

    当然,我的考虑中,是将所有的任务都变成Corn Expression,也就是说使用CornTrigger,SimpleTrigger没被使用,没这个使用的方便。

    大致效果如下:

    我们需要通过界面来增加要管理的任务:

     

    进一步考虑:

    也许现在我们只要配置一个Spring BEAN即可,也许将来,有人写了直接继承org.quartz.Job或者QuartzJobBean也要能支持:

      1: public class DemoJob extends AbstractJob {
    
      2: 
    
      3:   @Override
    
      4:   public void execute(JobDataMap jobDataMap) throws Exception {
    
      5:     logger.debug("DEMO JOB开始运行:"+jobDataMap.getWrappedMap());
    
      6: 
    
      7:   }
    
      8: 
    
      9: }

    也许有人写了个类,只想执行里面的一个方法也可以执行,如

      1: public class NonQuartzJob {
    
      2:   public void execute() {
    
      3:     System.out.println("NonQuartzJob Runned:"+jobEntityService);
    
      4:     try {
    
      5:       Thread.sleep(8000);
    
      6:     } catch (InterruptedException e) {
    
      7:       // TODO Auto-generated catch block
    
      8:       e.printStackTrace();
    
      9:     }
    
     10:     throw new RuntimeException("test");
    
     11:   }
    
     12: }

    现状:现在的Job都是独立实现,然后用spring配置式实现,都是采用如下方式配置

      1: <!-- 原始任务 -->
    
      2:   <bean id="queryStatementState" class="com.apusic.nsec.settlement.job.impl.QueryStatementState">
    
      3:     <property name="settlementService" ref="settlementService"></property>
    
      4:   </bean>
    
      5: <!-- 包装成Spring任务 -->
    
      6:   <bean name="checkDiskJob"
    
      7:     class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    
      8:     <property name="targetObject" ref="queryStatementState" />
    
      9:     <property name="targetMethod" value="queryStatement" />
    
     10:     <property name="concurrent" value="false" />
    
     11:   </bean>
    
     12: 
    
     13:   <!-- Trigger-->
    
     14:   <bean id="repeatTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    
     15:     <property name="jobDetail" ref="checkDiskJob" />
    
     16:     <!-- 5分钟后启动-->
    
     17:     <property name="startDelay" value="300000" />
    
     18:     <!--  30分钟检查一次-->
    
     19:     <property name="repeatInterval" value="1800000" />
    
     20:   </bean>
    
     21: 
    
     22:   <!-- 调度器-->
    
     23:   <bean id="scheduler"
    
     24:     class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    
     25:     <property name="triggers">
    
     26:       <list>
    
     27:         <ref bean="repeatTrigger" />
    
     28:       </list>
    
     29:     </property>
    
     30:   </bean>

    实现:

    1.实体定义:

      1: @Entity
    
      2: public class JobEntity extends IdEntity {
    
      3:   @NotBlank
    
      4:   @Column(unique = true)
    
      5:   private String jobName;// 任务名
    
      6:   @NotBlank
    
      7:   private String jobClass;// 类名或者bean名
    
      8:   private String jobMethod;// 如果为bean名,对应执行的方法
    
      9:   @NotNull
    
     10:   private String jobCronExpress;// 表达式
    
     11:   private String jobDesc;// 任务描述
    
     12:   private String jobGroupName;// Group名
    
     13:   @ElementCollection(fetch = FetchType.LAZY)
    
     14:   @CollectionTable(name = "t_job_properties")
    
     15:   private Map<String, String> properties = new HashMap<String, String>();
    
     16:   private int jobExecCount;
    
     17:   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    
     18:   @Temporal(TemporalType.TIMESTAMP)
    
     19:   private Date createTime = new Date();
    
     20:   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    
     21:   @Temporal(TemporalType.TIMESTAMP)
    
     22:   private Date lastExecTime;
    
     23:   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    
     24:   @Temporal(TemporalType.TIMESTAMP)
    
     25:   private Date nextExecTime;
    
     26:   // true=继承Job类,false=spring bean,没有继承job类
    
     27:   private boolean jobClassIsBeanName = false;
    
     28:   @Enumerated(EnumType.STRING)
    
     29:   private JobStatus status = JobStatus.WAITTING;
    
     30: 
    
     31:   private long jobUsedTime;// ms
    
     32:   private long jobExceptionCount;//任务异常总数
    
     33:   
    
     34:   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    
     35:   @Temporal(TemporalType.TIMESTAMP)
    
     36:   private Date lastExeceptionTime;

    日志记录

      1: @Entity
    
      2: public class JobLogEntity extends IdEntity {
    
      3:   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    
      4:   @Temporal(TemporalType.TIMESTAMP)
    
      5:   private Date execTime = new Date();
    
      6:   @Enumerated(EnumType.STRING)
    
      7:   private JobLogStatus status = JobLogStatus.SUCCESS;
    
      8: 
    
      9:   @ManyToOne
    
     10:   private JobEntity jobEntity = new JobEntity();
    
     11:   @Column(length = 4000)
    
     12:   private String exceptionStackTrace;

    2.接口定义

      1: 
    
      2: public interface QuartzFacade {
    
      3: 
    
      4:   public void startJobs(List<JobEntity> jobEntitys) throws SchedulerException, ClassNotFoundException,
    
      5:       NoSuchMethodException;
    
      6: 
    
      7:   public void startJob(JobEntity jobEntity) throws SchedulerException, ClassNotFoundException, NoSuchMethodException;
    
      8: 
    
      9:   public void startJobImmediatelyOnce(JobEntity jobEntity) throws SchedulerException, ClassNotFoundException,
    
     10:       NoSuchMethodException;
    
     11: 
    
     12:   public void startScheduler() throws SchedulerException;
    
     13: 
    
     14:   public void pauseJob(JobEntity jobEntity) throws SchedulerException;
    
     15: 
    
     16:   public void resumeJob(JobEntity jobEntity) throws SchedulerException;
    
     17: 
    
     18:   public void pauseAll() throws SchedulerException;
    
     19: 
    
     20:   public void shutdownAll() throws SchedulerException;
    
     21: 
    
     22:   public void saveJobEntity(JobEntity jobEntity);
    
     23: 
    
     24:   public void updateJobEntity(JobEntity jobEntity);
    
     25: 
    
     26:   public void removeJobEntity(JobEntity jobEntity);
    
     27: 
    
     28:   public void deleteById(Long id);
    
     29: 
    
     30:   public JobEntity getById(Long id);
    
     31: 
    
     32:   public JobEntity findJobEntityByJobName(String jobName);
    
     33: 
    
     34:   public List<JobEntity> getAllJobEntitys();
    
     35: 
    
     36:   public Page<JobEntity> getAllJobEntitysAsPage(Page<JobEntity> p);
    
     37: 
    
     38:   public boolean isExistJobEntity(String jobName);
    
     39: …log相关
    
     40: }

    3.实现

    为了方便记录日志,或者增加操作,如果用切面,没有通用性,还不如定义父类。

    3.1抽象JOB定义

    在此我们包装了JOB,之后的JOB都必须继承此类,之前的已定义的须做相应的转换:

      1: public abstract class AbstractJob extends QuartzJobBean {
    
      2:   protected final static Log logger = LogFactory.getLog(AbstractJob.class);
    
      3: 
    
      4:   @Override
    
      5:   protected final void executeInternal(JobExecutionContext context) throws JobExecutionException {
    
      6:     JobDetail jobDetail = context.getJobDetail();
    
      7: 
    
      8:     JobEntity jobEntity = getJobEntityService().findJobEntityByJobName(jobDetail.getKey().getName());
    
      9:     JobLogEntity logEntity = preExecute(context, jobEntity);
    
     10: 
    
     11:     try {
    
     12:       long start = System.currentTimeMillis();
    
     13: 
    
     14:       execute(jobDetail.getJobDataMap());//
    
     15: 
    
     16:       jobEntity.setJobUsedTime(System.currentTimeMillis() - start);
    
     17:       jobEntity.setStatus(JobStatus.WAITTING);
    
     18:       getJobEntityService().updateJobEntity(jobEntity);
    
     19:       getJobLogEntityService().addJobLog(logEntity);
    
     20:     } catch (Exception e) {
    
     21:       logger.error("任务执行出错" + e.getMessage(), e);
    
     22:       dealException(jobEntity, logEntity, e);
    
     23:       throw new JobExecutionException(e);
    
     24:     }
    
     25: 
    
     26:   }
    
     27: 
    
     28:   private JobLogEntity preExecute(JobExecutionContext context, JobEntity jobEntity) throws JobExecutionException {
    
     29:     if (jobEntity == null) {
    
     30:       logger.error("您要执行的任务不存在:" + context.getJobDetail().getName());
    
     31:       throw new JobExecutionException("任务不存在:" + context.getJobDetail().getName());
    
     32:     }
    
     33: 
    
     34:     jobEntity.setStatus(JobStatus.RUNNING);
    
     35:     jobEntity.setLastExecTime(new Date());
    
     36:     jobEntity.setNextExecTime(context.getNextFireTime());
    
     37:     jobEntity.setJobExecCount(jobEntity.getJobExecCount() + 1);
    
     38: 
    
     39:     JobLogEntity logEntity = new JobLogEntity();
    
     40:     logEntity.setJobEntity(jobEntity);
    
     41:     getJobEntityService().updateJobEntity(jobEntity);
    
     42:     return logEntity;
    
     43:   }
    
     44: 
    
     45:   private void dealException(JobEntity jobEntity, JobLogEntity logEntity, Exception e) throws JobExecutionException {
    
     46:     ExceptionEventDispather.getInstance().notify(jobEntity);
    
     47:     logEntity.setStatus(JobLogStatus.FAIL);
    
     48:     logEntity.setExceptionStackTrace(Util.getStackTrack(e));
    
     49:     try {
    
     50:       getJobLogEntityService().addJobLog(logEntity);
    
     51:       jobEntity.setJobExceptionCount(jobEntity.getJobExceptionCount() + 1);
    
     52:       jobEntity.setStatus(JobStatus.EXCEPTION);
    
     53:       jobEntity.setLastExeceptionTime(new Date());
    
     54:       getJobEntityService().updateJobEntity(jobEntity);
    
     55:     } catch (Exception e1) {
    
     56:       throw new JobExecutionException(e1);
    
     57:     }
    
     58:   }
    
     59: 
    
     60:   @Transactional
    
     61:   public abstract void execute(JobDataMap jobDataMap) throws Exception;

    同时定义了事件派发,以便任务出错,或者出错多少次时,发送邮件到管理员的邮件.

    Job代码:

      1: public class DemoSpringJob  extends AbstractJob {
    
      2:   @Autowired
    
      3:   JobEntityService jobEntityService;
    
      4:   @Override
    
      5:   public void execute(JobDataMap jobDataMap) throws Exception {
    
      6:     //JobEntity job = jobEntityService.findJobEntityByJobName("Demo任务_1325661555923");
    
      7:     logger.debug("DemoSpringJob Runned:"+jobEntityService);
    
      8:   }
    
      9:   public void setJobEntityService(JobEntityService jobEntityService) {
    
     10:     this.jobEntityService = jobEntityService;
    
     11:   }
    
     12: 
    
     13: 
    
     14: }
      1: <bean id="demoSpringJob" class="com.xia.quartz.job.DemoSpringJob">
    
      2:     <property name="jobEntityService" ref="jobEntityService"></property>
    
      3:   </bean>

    3.2 实体转换成任务Quartz.jobdetail

      1: private JobDetail convertJob(JobEntity jobEntity) throws ClassNotFoundException, NoSuchMethodException {
    
      2:     logger.debug("Job生成中:" + jobEntity.getJobName());
    
      3: 
    
      4:     JobDetail jobDetail;
    
      5:     if (jobEntity.isJobClassIsBeanName()) {//如果是spring bean
    
      6:       InvokerJobBean bean=ApplicationContextHolder.getBean("invokerJobBean");//通过invokerJobBean转换
    
      7:       bean.setTargetBeanName(jobEntity.getJobClass());
    
      8:       bean.setTargetMethod(jobEntity.getJobMethod());
    
      9:       bean.afterPropertiesSet();
    
     10:       jobDetail=bean.getObject();
    
     11:     } else {//如果是继承的是Job类
    
     12:       String jobClass = jobEntity.getJobClass();
    
     13:       @SuppressWarnings("unchecked")
    
     14:       Class<? extends Job> clazz = (Class<? extends Job>) Class.forName(jobClass);
    
     15:       jobDetail = ApplicationContextHolder.getBean("jobDetail");
    
     16:       jobDetail.setJobClass(clazz);
    
     17:     }
    
     18:     jobDetail.setName(jobEntity.getJobName());
    
     19:     jobDetail.setGroup(jobEntity.getJobGroupName());
    
     20:     jobDetail.setDescription(jobEntity.getJobDesc());
    
     21:     try {
    
     22:       jobDetail.getJobDataMap().putAll(jobEntity.getProperties());
    
     23:     } catch (Exception e) {
    
     24:       logger.error(e.getMessage());
    
     25:     }
    
     26:     return (JobDetail) jobDetail;
    
     27:   }

    3.3 开始任务

    在看此代码,你须了解Quartz的机制,它由三个东西组成:Scheduler,Trigger,JobDetail.对应作用是:

    执行线程,执行策略,执行内容。也就是,在哪个线程下,使用什么策略,执行什么内容。执行线程下可以有多个Trigger,一个trigger下可以多个任务。

    一般地,一个系统有一个Scheduler即可,而Trigger与JobDetail一般是一一对应的。毕竟不同任务执行周期都不同。

      1: public void startJob(JobEntity jobEntity) throws SchedulerException, ClassNotFoundException, NoSuchMethodException {
    
      2:     try {
    
      3:       JobDetail jobDetail = convertJob(jobEntity);
    
      4:       CronTrigger triggerCorn = convertTrigger(jobEntity);
    
      5:       if (StringUtils.isNotBlank(jobEntity.getJobGroupName())) {
    
      6:         jobEntity.setJobGroupName(jobDetail.getGroup());
    
      7:         jobEntityService.updateJobEntity(jobEntity);
    
      8:       }
    
      9:       //jobEntity.setStatus(JobStatus.WAITTING);
    
     10: 
    
     11:       Date ft = scheduler.scheduleJob(jobDetail, triggerCorn);
    
     12:       logger.debug("任务:" + jobDetail.getKey() + " will run at: " + ft.toLocaleString());
    
     13:     } catch (ParseException e) {
    
     14:       logger.error("任务转换失败:" + e.getMessage(), e);
    
     15:       throw new SchedulerException(e);
    
     16:     }
    
     17:   }

    执行所有任务:

      1: QuartzService quartzService = ApplicationContextHolder.getBean("quartzService");
    
      2:     JobEntityService jobService = ApplicationContextHolder.getBean("jobEntityService");
    
      3:     List<JobEntity> all = jobService.getAllJobEntitys();
    
      4:     quartzService.startJobs(all);

    3.3 状态变化

    暂停的任务,如果暂停有执行点,那么,你继续后,同样会执行一次此任务。

    其它状态变化在此没有列出。

      1: public void pauseJob(JobEntity jobEntity) throws SchedulerException {
    
      2:     logger.debug("暂停JOB:" + jobEntity);
    
      3:     scheduler.pauseJob(jobEntity.getJobName(), jobEntity.getJobGroupName());
    
      4:     // scheduler.interrupt(jobKey);
    
      5:     jobEntity.setStatus(JobStatus.PAUSED);
    
      6:     jobEntityService.updateJobEntity(jobEntity);
    
      7:   }

    3.4数据库数据如下:

     

    4.组件化

    将quartz相关代码独立成一个工程,对外提供quartzFacade的服务即可,包括JobEntity的crud,pagingList,Exception view,Log view,ExceptionEvent push.

    使用者要做的事就是,希望是开箱即用Out-Of-Box:

    • 引入工程
    • 增加spring配置
    • 注册一个ExceptionHandlerListener,
    • 使用quartzFacade服务

    5.遗留问题

    Spring bean 注入问题:

    因Quartz的机制里,执行的任务类是这么jobDetail.setJobClass(clazz);注入进来的,那么,这个clazz如果要注入service,只能通过手工注入,或者使用使用spring

      1: <bean id="jobDetailInvoker"
    
      2:     class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    
      3:     <property name="targetObject" ref="nonQuartzJob" />
    
      4:     <property name="targetMethod">
    
      5:       <value>execute</value>
    
      6:     </property>
    
      7:   </bean>

    而这个实现的原理是,把目标首丢过去,同时把要注入的东西丢到Quartz.job.contextMap中,在执行任务时,重装装配上去。

    以下是Spring处理的相关代码

    org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean:

      1: public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException {
    
      2:     prepare();
    
      3: 
    
      4:     // Use specific name if given, else fall back to bean name.
    
      5:     String name = (this.name != null ? this.name : this.beanName);
    
      6: 
    
      7:     // Consider the concurrent flag to choose between stateful and stateless job.
    
      8:     Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
    
      9: 
    
     10:     // Build JobDetail instance.
    
     11:     this.jobDetail = new JobDetail(name, this.group, jobClass);
    
     12:     this.jobDetail.getJobDataMap().put("methodInvoker", this);
    
     13:     this.jobDetail.setVolatility(true);
    
     14:     this.jobDetail.setDurability(true);
    
     15: 
    
     16:     // Register job listener names.
    
     17:     if (this.jobListenerNames != null) {
    
     18:       for (String jobListenerName : this.jobListenerNames) {
    
     19:         this.jobDetail.addJobListener(jobListenerName);
    
     20:       }
    
     21:     }
    
     22: 
    
     23:     postProcessJobDetail(this.jobDetail);
    
     24:   }
      1: public static class MethodInvokingJob extends QuartzJobBean {
    
      2: 
    
      3:     protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class);
    
      4: 
    
      5:     private MethodInvoker methodInvoker;
    
      6: 
    
      7:     /**
    
      8:      * Set the MethodInvoker to use.
    
      9:      */
    
     10:     public void setMethodInvoker(MethodInvoker methodInvoker) {
    
     11:       this.methodInvoker = methodInvoker;
    
     12:     }
    
     13: 
    
     14:     /**
    
     15:      * Invoke the method via the MethodInvoker.
    
     16:      */
    
     17:     @Override
    
     18:     protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    
     19:       try {
    
     20:         context.setResult(this.methodInvoker.invoke());
    
     21:       }
    
     22:       catch (InvocationTargetException ex) {
    
     23:         if (ex.getTargetException() instanceof JobExecutionException) {
    
     24:           // -> JobExecutionException, to be logged at info level by Quartz
    
     25:           throw (JobExecutionException) ex.getTargetException();
    
     26:         }
    
     27:         else {
    
     28:           // -> "unhandled exception", to be logged at error level by Quartz
    
     29:           throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException());
    
     30:         }
    
     31:       }
    
     32:       catch (Exception ex) {
    
     33:         // -> "unhandled exception", to be logged at error level by Quartz
    
     34:         throw new JobMethodInvocationFailedException(this.methodInvoker, ex);
    
     35:       }
    
     36:     }
    
     37:   }
      1: public abstract class QuartzJobBean implements Job {
    
      2: 
    
      3:   /**
    
      4:    * This implementation applies the passed-in job data map as bean property
    
      5:    * values, and delegates to <code>executeInternal</code> afterwards.
    
      6:    * @see #executeInternal
    
      7:    */
    
      8:   public final void execute(JobExecutionContext context) throws JobExecutionException {
    
      9:     try {
    
     10:       BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    
     11:       MutablePropertyValues pvs = new MutablePropertyValues();
    
     12:       pvs.addPropertyValues(context.getScheduler().getContext());
    
     13:       pvs.addPropertyValues(context.getMergedJobDataMap());
    
     14:       bw.setPropertyValues(pvs, true);
    
     15:     }
    
     16:     catch (SchedulerException ex) {
    
     17:       throw new JobExecutionException(ex);
    
     18:     }
    
     19:     executeInternal(context);
    
     20:   }
  • 相关阅读:
    TreeView中找鼠标指向的节点
    自己写的一个分页控件源代码
    [JWF]只显示当前用户的WorkItem方法
    [JWF]安装Workflow Server后的中文界面补丁
    [JWF]JWF中调用WebService方法
    [JWF]配置Adobe Form Server Application
    [导入](HOWTO)将一个Xml中的节点复制到别一个Xml的节点上
    [JWF]Form Common button 执行生命周期
    [JWF]Special Buttons 执行生命周期
    [JWF]Participant Interface访问ActiveDirectory
  • 原文地址:https://www.cnblogs.com/jijm123/p/16306334.html
Copyright © 2020-2023  润新知