论坛首页 Java企业应用论坛

spring+quartz 动态任务方案

浏览 22248 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (14)
作者 正文
   发表时间:2010-07-02  

   公司目前有这样的需求,结合spring+quartz开发个后台的WEB管理系统,系统主要包括以下功能:

  1、动态添加、修改和删除数据源,并加入到spring管理。
  2、动态注册、修改和删除任务(需要实现的具体quartz业务类),并加入到quartz管理。
  3、提供上传第三方包的功能。(主要考虑实现的业务类,需要引入其他的jar包)。
  4、在线日志查询分析。
  。。。

   后台系统的应用领域:

  1、执行多个数据库之间的数据交换服务。
  2、架设系统与银行之间的通讯服务。
  。。。

  以前没搞过这方面应用,比较头疼,经过google、百度,初步方案实现如下:
 
  1、实现个servlet用于启动quartz调度。
  
 
  程序如下:
 
   public class DispatchJobServlet extends HttpServlet {

	private static final long serialVersionUID = -3920177706344758439L;
	private ApplicationContext ctx;

	public DispatchJobServlet() {
		super();
		//  初始化自定义类加载器,主要用于加载第三方包和服务程序。
		ServiceStartup manguage = new ServiceStartup();
		manguage.startUp();
	}

	/**
	 * 初始化
	 */
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		ctx = WebApplicationContextUtils
				.getRequiredWebApplicationContext(config.getServletContext());
		StartJobService taskStartService = (StartJobService) ctx.getBean("startJobService");
		//启用个线程开始任务调度
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(taskStartService);
	}
}
  


   2、到xml文件查询前台添加的任务队列,加入quartz管理并执行。
   
   
@Service
public class StartJobService implements Runnable {
	/**
	 * log4j 记录器
	 */
	private static final Logger log = Logger.getLogger(StartJobService.class);
	@Resource
	private SchedulerService schedulerService;

	public void run() {
		try {
			while (true) {
				List<JobBo> list = DomParser.findAllJobXml("db.service.xml");
				Iterator<JobBo> i = list.iterator();
				while (i.hasNext()) {
					JobBo job = i.next();
					// 如果任务队列不存在则注册并运行
					if (!ServiceManager.queryExistsJob(job.getName())) {
						try {
							schedulerService.schedule(job);
							ServiceManager.getJobHolder().put(job.getName(),
									job);
						} catch (Exception e) {
							log.error("服务【" + job.getName() + "】启动失败!", e);
							throw new SummerException("服务【" + job.getName()
									+ "】启动失败!");
						}
					}
					Thread.sleep(3000);
				}
			}
		} catch (SummerException e) {
			throw e;
		} catch (Exception e) {
			log.error("调度任务出现异常!", e);
		}
	}
}

   3、封装SchedulerService实现quartz调度的方法
   封装的出来quartz的接口:
  
public interface SchedulerService {

	/**
	 * 自定义任务对象并启动任务
	 * 
	 * @param job
	 *            任务队列业务对象
	 */
	void schedule(JobBo job);

	/**
	 * 取得所有调度Triggers
	 * 
	 * @return
	 */
	List<Map<String, Object>> getQrtzTriggers();

	/**
	 * 根据名称和组别暂停Tigger
	 * 
	 * @param triggerName
	 * @param group
	 */
	void pauseTrigger(String triggerName, String group);

	/**
	 * 恢复Trigger
	 * 
	 * @param triggerName
	 * @param group
	 */
	void resumeTrigger(String triggerName, String group);

	/**
	 * 删除Trigger
	 * 
	 * @param triggerName
	 * @param group
	 */
	boolean removeTrigdger(String triggerName, String group);
}


   实现类:
 
public class SchedulerServiceImpl implements SchedulerService {

	private static final Logger log = LoggerFactory
			.getLogger(SchedulerServiceImpl.class);

	private Scheduler scheduler;

	private JobDetail jobDetail;

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	public void setJobDetail(JobDetail jobDetail) {
		this.jobDetail = jobDetail;
	}

	/**
	 * 自定义任务对象并启动任务
	 */
	public void schedule(JobBo job) {
		// trigger分类
		String category = job.getCategory();
		try {
			if ("cron".equals(category)) {
				scheduleCron(job);
			} else {
				scheduleSimple(job);
			}
		} catch (Exception e) {
			log.error("任务调度过程中出现异常!");
			throw new SummerException(e);
		}
	}

	/**
	 * simple任务触发
	 * 
	 * @param job
	 */
	private void scheduleSimple(JobBo job) {
		String name = getTriggerName(job.getName());
		// 实例化SimpleTrigger
		SimpleTrigger simpleTrigger = new SimpleTrigger();

		// 这些值的设置也可以从外面传入,这里采用默放值
		simpleTrigger.setJobName(jobDetail.getName());
		simpleTrigger.setJobGroup(Scheduler.DEFAULT_GROUP);
		simpleTrigger.setRepeatInterval(1000L);
		// 设置名称
		simpleTrigger.setName(name);

		// 设置Trigger分组
		String group = job.getGroup();
		if (StringUtils.isEmpty(group)) {
			group = Scheduler.DEFAULT_GROUP;
		}
		simpleTrigger.setGroup(group);

		// 设置开始时间
		Timestamp startTime = job.getStartTime();
		if (null != startTime) {
			simpleTrigger.setStartTime(new Date());
		}

		// 设置结束时间
		Timestamp endTime = job.getEndTime();
		if (null != endTime) {
			simpleTrigger.setEndTime(endTime);
		}

		// 设置执行次数
		int repeatCount = job.getRepeatCount();
		if (repeatCount > 0) {
			simpleTrigger.setRepeatCount(repeatCount);
		}

		// 设置执行时间间隔
		long repeatInterval = job.getRepeatInterval();
		if (repeatInterval > 0) {
			simpleTrigger.setRepeatInterval(repeatInterval * 1000);
		}
		try {
			JobDataMap jobData = new JobDataMap();
			jobData.put("name", job.getName());
			jobData.put("desc", job.getDesc());
			jobDetail.setJobDataMap(jobData);
			scheduler.addJob(jobDetail, true);
			scheduler.scheduleJob(simpleTrigger);
			scheduler.rescheduleJob(simpleTrigger.getName(), simpleTrigger
					.getGroup(), simpleTrigger);
		} catch (SchedulerException e) {
			log.error("任务调度出现异常!");
			log.error(LogGenerator.getInstance().generate(e));
			throw new SummerException("任务调度出现异常!");
		}
	}

	/**
	 * cron任务触发
	 * 
	 * @param job
	 */
	private void scheduleCron(JobBo job) {
		String name = getTriggerName(job.getName());
		try {
			JobDataMap jobData = new JobDataMap();
			jobData.put("name", job.getName());
			jobData.put("desc", job.getDesc());
			jobDetail.setJobDataMap(jobData);
			scheduler.addJob(jobDetail, true);
			CronTrigger cronTrigger = new CronTrigger(name, job.getGroup(),
					jobDetail.getName(), Scheduler.DEFAULT_GROUP);
			cronTrigger.setCronExpression(job.getCronExpression());
			scheduler.scheduleJob(cronTrigger);
			scheduler.rescheduleJob(cronTrigger.getName(), cronTrigger
					.getGroup(), cronTrigger);
		} catch (Exception e) {
			log.error("执行cron触发器出现异常!", e);
			throw new SummerException("执行cron触发器出现异常!");
		}
	}

	public void schedule(String name, Date startTime, Date endTime,
			int repeatCount, long repeatInterval, String group) {
		if (name == null || name.trim().equals("")) {
			name = UUID.randomUUID().toString();
		} else {
			// 在名称后添加UUID,保证名称的唯一性
			name += "&" + UUID.randomUUID().toString();
		}
		try {
			scheduler.addJob(jobDetail, true);
			SimpleTrigger SimpleTrigger = new SimpleTrigger(name, group,
					jobDetail.getName(), Scheduler.DEFAULT_GROUP, startTime,
					endTime, repeatCount, repeatInterval);
			scheduler.scheduleJob(SimpleTrigger);
			scheduler.rescheduleJob(SimpleTrigger.getName(), SimpleTrigger
					.getGroup(), SimpleTrigger);
		} catch (SchedulerException e) {
			throw new RuntimeException(e);
		}
	}

	public void pauseTrigger(String triggerName, String group) {
		try {
			scheduler.pauseTrigger(triggerName, group);// 停止触发器
		} catch (SchedulerException e) {
			throw new SummerException(e);
		}
	}

	public void resumeTrigger(String triggerName, String group) {
		try {
			scheduler.resumeTrigger(triggerName, group);// 重启触发器
		} catch (SchedulerException e) {
			log.error("重启触发器失败!");
			throw new SummerException(e);
		}
	}

	public boolean removeTrigdger(String triggerName, String group) {
		try {
			scheduler.pauseTrigger(triggerName, group);// 停止触发器
			return scheduler.unscheduleJob(triggerName, group);// 移除触发器
		} catch (SchedulerException e) {
			throw new SummerException(e);
		}
	}

	private Timestamp parseDate(String time) {
		try {
			return Timestamp.valueOf(time);
		} catch (Exception e) {
			log.error("日期格式错误{},正确格式为:yyyy-MM-dd HH:mm:ss", time);
			throw new SummerException(e);
		}
	}

	public List<Map<String, Object>> getQrtzTriggers() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 获取trigger名称
	 * 
	 * @param name
	 * @return
	 */
	private String getTriggerName(String name) {
		if (StringUtils.isBlank(StringUtils.trim(name))) {
			name = UUID.randomUUID().toString();
		} else {
			name = name.substring(name.lastIndexOf(".") + 1);
			// 在名称后添加UUID,保证名称的唯一性
			name += "&" + UUID.randomUUID().toString();
		}
		return StringUtils.trim(name);
	}
}


  4、覆盖QuartzJobBean的executeInternal方法,根据“name”名实现任务的动态分配
public class EnhanceQuartzJobBean extends QuartzJobBean {
	/**
	 * log4j 记录器
	 */
	private static final Logger log = Logger
			.getLogger(EnhanceQuartzJobBean.class);

	@Override
	protected void executeInternal(JobExecutionContext context)
			throws JobExecutionException {
		try {
			JobDetail t = context.getJobDetail();
			JobDataMap map = t.getJobDataMap();
			String name = map.getString("name");
			String desc = map.getString("desc");
			ServiceStartupManguage manguage = new ServiceStartupManguage();
			manguage.runService(name, desc);
		} catch (Exception e) {
			log.error("执行任务出现异常", e);
		}
	}
	
	public class ServiceStartup {
	/**
	 * log4j 记录器
	 */
	private static final Logger log = Logger
			.getLogger(ServiceStartupManguage.class);

	public void startUp() {
		// 启动动态重新加载类的服务
		StringBuilder sb = new StringBuilder(1024);
		sb.append(ServiceManager.getHome() + "work");
		String jarPath = ServiceManager.getHome() + "ext";
		// 遍历ext文件夹,寻找jar文件
		File dir = new File(jarPath);
		String[] subFiles = dir.list();
		for (int i = 0; i < subFiles.length; i++) {
			File file = new File(jarPath + System.getProperty("file.separator")
					+ subFiles[i]);
			if (file.isFile() && subFiles[i].endsWith("jar")) {
				sb.append(File.pathSeparator + jarPath
						+ System.getProperty("file.separator") + subFiles[i]);
			}
		}
		ServiceManager.checker = new ClassModifyChecker(ServiceManager.getHome());
		ServiceManager.loader = new ServiceClassLoad(DispatchJobServlet.class
				.getClassLoader(), (String) sb.toString(), ServiceManager.checker);
		ServiceManager.classPath = sb.toString();
	}

	/**
	 * 启动后台服务
	 * 
	 * @author 任鹤峰 2009-02-03
	 * @param name
	 * @param desc
	 * @throws ClassNotFoundException
	 * @throws NoSuchMethodException
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	public void runService(String name, String desc)
			throws ClassNotFoundException, NoSuchMethodException,
			InstantiationException, IllegalAccessException,
			InvocationTargetException {
		try {
			Object service;
			Class cls = null;
			if (null != ServiceManager.loader) {
				cls = ServiceManager.getLoader().loadClass(name);
			} else {
				cls = Class.forName(name);
			}
			Class[] par = null;
			Object[] obj = null;
			par = new Class[2];
			par[0] = String.class;
			par[1] = String.class;
			obj = new Object[2];
			obj[0] = name;
			obj[1] = desc;
			Constructor ct = cls.getConstructor(par);
			service = ct.newInstance(obj);
			Method meth = cls.getMethod("start");
			meth.invoke(service);
			cls = null;
		} catch (Exception e) {
			log.error("运行注册服务【" + name + "】出现异常", e);
		}
	}
}
	
}


  5、quartz的配置文件:
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
 "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="schedulerFactory" singleton="false"
		class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
		<property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
		<property name="configLocation" value="classpath:quartz.properties" />
	</bean>

	<bean id="jobDetail" singleton="false"
		class="org.springframework.scheduling.quartz.JobDetailBean">
		<property name="jobClass">
			<value>
				com.xeranx.summer.scheduling.EnhanceQuartzJobBean
            </value>
		</property>
	</bean>
	<bean id="schedulerService" singleton="false"
		class="com.xeranx.summer.scheduling.service.SchedulerServiceImpl">
		<property name="jobDetail">
			<ref bean="jobDetail" />
		</property>
		<property name="scheduler">
			<ref bean="schedulerFactory" />
		</property>
	</bean>
</beans>


以上是实现的主要代码: 目前可以实现任务的动态添加并执行,现在的问题是添加多个任务时,最后面的任务会覆盖之前所有的任务。

检查了N久未果,还请大家帮忙分析,或者对后台管理系统有更好的需求方案。


 
  
  
   发表时间:2010-07-02  
  没人感兴趣,我自己顶下。
0 请登录后投票
   发表时间:2010-07-02  
ibadboy 写道
  没人感兴趣,我自己顶下。

quartz+ant+ant-contrib 这才是正确的解决之道,欲活用,单独quartz是要走很多弯路的
0 请登录后投票
   发表时间:2010-07-03  
不是没人感兴趣,而是你自己说话的方式不对.
你第一点做的还行,把目标提出来了.可是你不说自己的想法和办法,直接就是一坨代码... ...
0 请登录后投票
   发表时间:2010-07-03  
JE帐号 写道
不是没人感兴趣,而是你自己说话的方式不对.
你第一点做的还行,把目标提出来了.可是你不说自己的想法和办法,直接就是一坨代码... ...

谢谢你的提点,可能我比较仓促没把问题描述清楚。


我一开始的思想,通过web.xml加载启动一个servlet开始整个任务调度,通过前台管理界面把注册的quartz服务添加到一个xml文件中,然后启动的线程解析xml数据,把服务添加到quartz任务队列,循环分别调用封装的 schedulerService.schedule(job)方法启动quartz的trigger。

其中schedule方法中根据Spring注入的jobDetail类,去执行jobclass中定义的EnhanceQuartzJobBean类,该类是我继承自Spring的QuartzJobBean类。

  现在问题的关键是,我所有的quartz服务都是通过EnhanceQuartzJobBean类中executeInternal方法调度。在executeInternal方法中我会根据前台注册的“服务名”动态初始化,并执行初始化类的相关的业务。也就是下面这段通过反射执行的代码:

  <code> public void runService(String name, String desc)  
            throws ClassNotFoundException, NoSuchMethodException,  
           InstantiationException, IllegalAccessException,  
           InvocationTargetException {  
        try {  
           Object service;  
           Class cls = null;  
          if (null != ServiceManager.loader) {  
               cls = ServiceManager.getLoader().loadClass(name);  
          } else {  
              cls = Class.forName(name);  
          }  
           Class[] par = null;  
         Object[] obj = null;  
          par = new Class[2];  
          par[0] = String.class;  
           par[1] = String.class;  
         obj = new Object[2];  
          obj[0] = name;  
           obj[1] = desc;  
          Constructor ct = cls.getConstructor(par);  
           service = ct.newInstance(obj);  
           Method meth = cls.getMethod("start");  
          meth.invoke(service);  
           cls = null;  
        } catch (Exception e) {  
           log.error("运行注册服务【" + name + "】出现异常", e);  
       }  
    }   </code>

  目前任务都可以跑,只不过多个任务一起跑的话,最后面的会覆盖前面所有的服务,而一直跑下去,前面的服务确已经停止。

  我分析下来,估计就是EnhanceQuartzJobBean类的问题,因为我所有的quartz服务都是通过调用该类去执行的。

  不知道大家清楚没有,还请赐教,或者有其他的解决方案。
 
0 请登录后投票
   发表时间:2010-07-03  
楼主公司项目的特性,刚好osgi架构适合你公司的要求.
1 请登录后投票
   发表时间:2010-07-03  
以前我写过一个,其实不难实现!好好想想就行了
1 请登录后投票
   发表时间:2010-07-04  
楼主封装得不错,可以参考下国内开源框架甲壳虫j2ee框架里面对Quartz的封装。
0 请登录后投票
   发表时间:2010-07-04   最后修改:2010-07-04

很明显,你的jobDetail是通过spring注入进去的,然后你所有的 

 

scheduler.addJob(jobDetail, true); 

方法中的jobDetail一直是同一个。。

 

so....长江后浪推前浪了....

 

解决办法就是你动态产生一个jobDetail 就OK了

 

一个jobDetail就是一个任务,一个jobDetail 对应N个 Trigger

 

 

0 请登录后投票
   发表时间:2010-07-04  
spring3里面 一个annotation就搞定咯
看看spring reference的例子吧
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics