spring 定时任务job(quartz)可视化

ScarletLina 2018-10-26

本文介绍的是使用spring(spring继承并简化的quartz)的作业框架时,作业的可视化(管理,本文的可视化管理做的不完善)

解决办法就是使用监听器,实现job历史记录、job统计、异常记录、手动触发job

先来看一下 实现的流程

spring 定时任务job(quartz)可视化

这里只需要自己实现几个监听器并注册即可

- StatisticSchedulerListener 监听触发规则triggers,一般是在spring启动和刷新时触发

- StatisticJobListener JOB监听(监听JOB执行前和执行后)

- StatisticTriggerListener

这里主要使用 StatisticJobListener

public class StatisticJobListener implements JobListener {

private static Logger log = Logger.getLogger(StatisticJobListener.class);

private static ThreadLocal<JobStatisticBean> threadLocal = new ThreadLocal<JobStatisticBean>();

private String name;

public void setName(String name) {

this.name = name;

}

@Override

public String getName() {

return this.name;

}

//beginging

@Override

public void jobToBeExecuted(JobExecutionContext context) {

log.info("JOB将要执行... "+context.getJobDetail().getName());

JobStatisticBean jobStatisticBean = new JobStatisticBean();

jobStatisticBean.setJobName(context.getJobDetail().getName());

jobStatisticBean.setJobDetail(context.getJobDetail());

jobStatisticBean.setTrigger(context.getTrigger());

jobStatisticBean.setStartTime(new Timestamp(System.currentTimeMillis()));

jobStatisticBean.setJobExecutionContext(context);

jobStatisticBean.setJobIntance(context.getJobInstance());

threadLocal.set(jobStatisticBean);

}

@Override

public void jobExecutionVetoed(JobExecutionContext context) {

log.info("jobExecutionVetoed "+context.getJobDetail().getName());

}

@Override

public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {

log.info("JOB执行完毕 "+context.getJobDetail().getName());

JobStatisticBean jobStatisticBean = threadLocal.get();

if(jobStatisticBean!=null){

jobStatisticBean.setEndTime(new Timestamp(System.currentTimeMillis()));

jobStatisticBean.setTakesTime(jobStatisticBean.getEndTime().getTime()-jobStatisticBean.getStartTime().getTime());

StatisticProcessor.add(jobStatisticBean);

}

}

}

具体的缓存可以自己扩展,这里就是简单的使用了当前的jvm缓存,自己可以扩展Redis

public class StatisticProcessor extends Thread implements ApplicationContextAware, InitializingBean, DisposableBean {

private static Logger logJob = Logger.getLogger(LoggerCategory.JOB_STATISTIC);

private static Logger logJobHistory = Logger.getLogger(LoggerCategory.JOB_STATISTIC_HISTORY);

private static Logger logJobException = Logger.getLogger(LoggerCategory.JOB_STATISTIC_EXCEPTION);

private static volatile Queue<JobStatisticBean> jobQueue = new ConcurrentLinkedQueue<JobStatisticBean>();

private static volatile Queue<SchedulerExceptionBean> exceptionQueue = new ConcurrentLinkedQueue<SchedulerExceptionBean>();

private static volatile Map<String, JobStatisticBean> jobMap = new ConcurrentHashMap<String, JobStatisticBean>();

/**

* 手动触发JOB

* @param jobStatisticBean

* @return

* @throws Exception

*/

public static boolean runByHand(JobStatisticBean jobStatisticBean) throws Exception{

if(jobStatisticBean==null)

return false;

JobExecutionContext context = jobStatisticBean.getJobExecutionContext();

Job job = jobStatisticBean.getJobIntance();

job.execute(context);

return true;

}

/**

* 添加一个JOB的实例(使用map防止重复),并记录JOB执行记录(Queue)

* @param jobStatisticBean

* @return

*/

public static boolean add(JobStatisticBean jobStatisticBean){

if(!jobMap.containsKey(jobStatisticBean.getJobName())) {

logJob.info("add job "+ JacksonUtil.toJsonString(jobStatisticBean));

}

jobMap.put(jobStatisticBean.getJobName(), jobStatisticBean);

logJobHistory.info(JacksonUtil.toJsonString(jobStatisticBean));

return jobQueue.add(jobStatisticBean);

}

/**

* 添加执行异常LOG

* @param schedulerExceptionBean

* @return

*/

public static boolean add(SchedulerExceptionBean schedulerExceptionBean){

logJobException.info(JacksonUtil.toJsonString(schedulerExceptionBean));

return exceptionQueue.add(schedulerExceptionBean);

}

public static List<JobStatisticBean> getALlJobs() {

int size = jobMap.size();

size = size>100? 100:size;

return Arrays.asList(jobMap.values().toArray(new JobStatisticBean[size]));

}

public static List<JobStatisticBean> getAllJobHistory() {

int size = jobQueue.size();

size = size>100? 100:size;

return Arrays.asList(jobQueue.toArray(new JobStatisticBean[size]));

}

public static List<SchedulerExceptionBean> getAllExceptions(){

int size = exceptionQueue.size();

size = size>100? 100:size;

return Arrays.asList(exceptionQueue.toArray(new SchedulerExceptionBean[size]));

}

private volatile boolean exit = false;

private ApplicationContext applicationContext = null;

private static volatile StatisticProcessor processorIntance = null;

public synchronized void init(){

if(processorIntance==null) {

logJob.info("--------------------- "+new Date().toLocaleString()+" --------------------------");

logJobHistory.info("--------------------- "+new Date().toLocaleString()+" --------------------------");

logJobException.info("--------------------- "+new Date().toLocaleString()+" --------------------------");

processorIntance = new StatisticProcessor();

processorIntance.setName(GConstants.THREA_HEAD + "JobStatisticProcessor");

processorIntance.setDaemon(true);

processorIntance.start();

}

}

@Override

public void run() {

do{

try {

Thread.sleep(1000*30);

synchronized (jobQueue) {

if (!jobQueue.isEmpty()) {

long outSize = jobQueue.size() - 100;

while (outSize-- > 0) {

jobQueue.poll();

}

}

}

synchronized (exceptionQueue) {

if (!exceptionQueue.isEmpty()) {

long outSize = exceptionQueue.size() - 100;

while (outSize-- > 0) {

exceptionQueue.poll();

}

}

}

}catch (Exception e){

e.printStackTrace();

}

}while( !this.exit);

}

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

this.applicationContext = applicationContext;

}

@Override

public void afterPropertiesSet() throws Exception {

this.init();

}

@Override

public void destroy(){

this.exit = true;

}

}

public class JobStatisticBean implements Serializable {

private static long serialVersionUID = -1L;

private String jobName;

private String className;

private Timestamp startTime;

private Timestamp endTime;

private Long takesTime;

private JobDetail jobDetail;

private Trigger trigger;

private JobExecutionContext jobExecutionContext;

private Job jobIntance;

//.. getter & setter

}

job.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false">

<property name="schedulerName" value="EZhe-taks" />

<property name="triggers">

<list>

<ref bean="task1" />

<ref bean="task2" />

</list>

</property>

<property name="globalJobListeners" >

<list>

<bean class="com.gozap.ezhe.task.listener.StatisticJobListener" >

<property name="name" value="JOB统计" />

</bean>

</list>

</property>

<property name="schedulerListeners">

<list>

<bean class="com.gozap.ezhe.task.listener.StatisticSchedulerListener">

</bean>

</list>

</property>

<property name="autoStartup" value="true"/>

<property name="configLocation" value="classpath:quartz.properties" />

</bean>

<!--自动更新汇率任务-->

<bean id="task1" class="org.springframework.scheduling.quartz.CronTriggerBean">

<property name="jobDetail" >

<bean id="jobUpdateCurrencyTask" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="name" value="自动更新汇率任务" />

<property name="jobClass" value="com.gozap.ezhe.task.CurrencyUpdateTask"></property>

</bean>

</property>

<property name="cronExpression" value=" 0 0/30 * * * ?"/>

</bean>

<!--自动xx的任务-->

<bean id="task2" class="org.springframework.scheduling.quartz.CronTriggerBean">

<property name="jobDetail" >

<bean id="logisticsScheduleTask" class="org.springframework.scheduling.quartz.JobDetailBean">

<property name="name" value="自动xx的任务" />

<property name="jobClass" value="com.gozap.ezhe.task.LogisticsScheduleTask"></property>

</bean>

</property>

<property name="cronExpression" value="0 0 0/2 * * ?"/>

</bean>

</beans>

JobStatisticAction.java

public class JobStatisticAction extends BaseAction {

private List<JobStatisticBean> jobList;

private List<JobStatisticBean> jobHistoryList;

private List<SchedulerExceptionBean> jobExceptionList;

public String jobStatistic() throws Exception {

jobList = StatisticProcessor.getALlJobs();

jobHistoryList = StatisticProcessor.getAllJobHistory();

Collections.reverse(jobHistoryList);

jobExceptionList = StatisticProcessor.getAllExceptions();

return Action.SUCCESS;

}

//============================

public List<JobStatisticBean> getJobList() {

return jobList;

}

public List<JobStatisticBean> getJobHistoryList() {

return jobHistoryList;

}

public List<SchedulerExceptionBean> getJobExceptionList() {

return jobExceptionList;

}

}

job.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ taglib uri="/struts-tags" prefix="s" %>

<%@ page import="java.text.SimpleDateFormat" %>

<%@ page import="java.util.Date" %>

<%

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

String nowTime = sdf.format(new Date());

%>

<html>

<head>

<title>Job Statistic</title>

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<!-- 新 Bootstrap 核心 CSS 文件 -->

<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css">

<!-- 可选的Bootstrap主题文件(一般不用引入) -->

<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap-theme.min.css">

<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->

<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>

<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->

<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

<style>

table{

font-size: smaller;

}

</style>

</head>

<body>

<div class="container ">

<div class="panel panel-success">

<div class="panel-heading">

<h2 class="text-center">Job List</h2>

</div>

<div class="panel-body">

<table class="table-hover table">

<tr>

<th>序号</th>

<th style="width: 200px;">任务名称</th>

<th style="min-width: 180px;">最近执行时间</th>

<th style="width: 100px;">耗时(ms)</th>

<th style="width: 100px;">More [系统时间: <font color="red" font-size="120%"> <%=nowTime%> </font> ]</th>

</tr>

<s:iterator value="jobList" id="jobList" status="step">

<tr>

<td>${step.index+1}</td>

<td>${jobName}</td>

<td>${startTime} - ${endTime}</td>

<td>${takesTime}</td>

<td>

<table style="font-size: 100%;">

<tr>

<td align="right">Class :</td>

<td> ${jobDetail.jobClass} </td>

</tr>

<tr>

<td align="right">CronEx :</td>

<td> ${trigger.cronExpression} </td>

</tr>

<tr>

<td align="right">NextFireTime :</td>

<td> <s:date name="trigger.nextFireTime" format="yyyy-MM-dd HH:mm:ss.SSS" /></td>

</tr>

<tr>

<td align="right">PreFireTime :</td>

<td> <s:date name="trigger.previousFireTime" format="yyyy-MM-dd HH:mm:ss.SSS" /> </td>

</tr>

<tr>

<td align="right">StartTime :</td>

<td> <s:date name="trigger.startTime" format="yyyy-MM-dd HH:mm:ss.SSS" /> </td>

</tr>

</table>

</td>

</tr>

</s:iterator>

</table>

</div>

</div>

<div class="panel panel-danger">

<div class="panel-heading">

<h2 class="text-center">Job Error</h2>

</div>

<div class="panel-body">

<table class="table-hover table">

<tr>

<th>序号</th>

<th style="width: 200px;">错误内容</th>

<th style="min-width: 180px;">堆栈</th>

</tr>

<s:iterator value="jobExceptionList" id="jobList" status="step">

<tr>

<td>${step.index+1}</td>

<td>${msg}</td>

<td>${exception}</td>

</tr>

</s:iterator>

</table>

</div>

</div>

<div class="panel panel-info">

<div class="panel-heading">

<h2 class="text-center">Job History</h2>

</div>

<div class="panel-body">

<table class="table-hover table">

<tr>

<th>序号</th>

<th style="width: 200px;">任务名称</th>

<th style="min-width: 180px;">执行时间</th>

<th style="width: 100px;">耗时(ms)</th>

<th style="width: 100px;">class</th>

</tr>

<s:iterator value="jobHistoryList" id="jobList" status="step">

<tr>

<td>${step.index+1}</td>

<td>${jobName}</td>

<td>${startTime} - ${endTime}</td>

<td>${takesTime}</td>

<td>${jobDetail.jobClass}</td>

</tr>

</s:iterator>

</table>

</div>

</div>

</div>

</body>

</html>

本文介绍的只是一个简单的实现,推荐使用设计模式的一些方式进行重构,如果以后有时间我也会重构

相关推荐