热部署定时任务管理实现(上传jar+动态加载jar中接口类文件+hessian远程调用执行业务)
1.新增定时任务
主要是配置定时任务的执行周期,涉及到热部署的就是关于接口jar上传,要调用的远程接口类名称,接口中要被调用的方法名称、远程调用的hessianURL,这样子就配置好了一个定时任务,信息存放到数据库,这样就成功添加了一个定时任务
用到了loadJar这个方法,这里用到了动态加载,刚上传的jar文件要被动态的加载到JVM中,使用了URLClassLoader根据类名称读取jar中指定的类,并且返回加载相应jar的这个URLClassLoader,因为后面用到这个加载的类的时候,必须要用加载它的类加载器才行,否则会报ClassNotFoundException
Java代码
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.net.URL;
- import java.net.URLClassLoader;
- import org.apache.struts2.ServletActionContext;
- public class JarUtil {
- // public final static String PATH = ServletActionContext.getServletContext().getRealPath("/")+"uploadJar"+File.separator;
- public final static String PATH = "d:/uploadJar"+File.separator;
- private static void uploadJar(File jar,String filePathName) {
- try {
- FileInputStream fin = new FileInputStream(jar);//获得上传的jar文件
- File dir = new File(PATH);
- if(!dir.exists()){
- dir.mkdir();
- }
- FileOutputStream fout = new FileOutputStream(new File(filePathName));//输出jar到指定位置
- byte[] buffer = new byte[1024];
- int count = fin.read(buffer);
- while (count > 0) {
- fout.write(buffer,0,count);//这里一定要标注0,count,否则jar实际上是有问题的。导致后面的jar无法加载
- count = fin.read(buffer);
- }
- fout.flush();
- fout.close();
- fin.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- *
- * @param jar 需要加载的jar文件
- * @param jarFileName jar文件的名称
- * @param className 需要加载的jar中的class的二进制名称
- * @return
- */
- public static URLClassLoader loadJar(File jar,String jarFileName,String className){
- String filePathName = PATH + jarFileName; //得到服务器上要放置的文件路径,给jar添加一个加入时刻防止名称重复
- uploadJar(jar,filePathName);//上传jar
- return loadClassOfJar(filePathName,className) ;//加载jar中的执行class文件
- }
- /**
- *
- * @param url 指向指定jar文件的URL
- * @param cls 加载jar文件中的class文件的二进制名称
- * @return 加载这个class对象的classloader
- */
- public static URLClassLoader loadClassOfJar(String filePathName, String cls) {
- URLClassLoader loader = null;
- try {
- File file = new File(filePathName);//加载这个刚上传的jar文件
- if(file != null){
- URL url = file.toURI().toURL();
- loader = new URLClassLoader(new URL[] { url },Thread.currentThread().getContextClassLoader());
- loader.loadClass(cls); // 动态装载class
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- return loader;
- }
- }
这就是web加载的时候就要启动所有已经添加的定时任务
这里要注意的是因为上传的jar文件是在一个特定的地方,不在war包中,需要启动的时候去动态加载,首先从数据库读出之前配置的定时任务,然后就是使用jarUtil的loadClassOfJar,把之前上传的jar文件里面的指定类加载,并把加载该类的相应的类加载器classLoader放到jobDetail的jobDataMap中,后面定时任务的执行的时候会用到,这里要注意这个TempleTask类,它是一个模板,只要是通过上传jar包来进行配置的定时任务都用这个高度抽象的模板,它就时一个job
Java代码
- import java.net.URLClassLoader;
- import java.text.ParseException;
- import java.util.ArrayList;
- import java.util.List;
- import org.quartz.CronTrigger;
- import org.quartz.JobDataMap;
- import org.quartz.JobDetail;
- import org.quartz.Scheduler;
- import org.quartz.SchedulerException;
- import com.tt.TimeTaskArgDao;
- import com.tt.model.TimeTaskArg;
- import com.tt.TempleTask;
- /**
- * 初始化trigger
- * @author tongwenhuan
- *
- */
- public class SchedulerInit {
- private Scheduler scheduler;
- private TimeTaskArgDao timeTaskArgDao;
- public void init() {
- try {
- String[] groupNames = scheduler.getJobGroupNames();
- for(String groupName : groupNames) {
- String[] jobNames = scheduler.getJobNames(groupName);
- if(jobNames.length > 0){
- List jns = new ArrayList();
- for(String jobName : jobNames) {
- jns.add(jobName);
- }
- List tta_list = timeTaskArgDao.listTimeTaskArgByJobName(jns);
- for(TimeTaskArg tta : tta_list) {
- CronTrigger cronTrigger = new CronTrigger(tta.getTriggerName(),groupName,tta.getJobName(),groupName,tta.getCronexpression());
- scheduler.scheduleJob(cronTrigger);
- if(!tta.getTriggerStatus().equals("0")) {
- scheduler.pauseTrigger(tta.getTriggerName(), tta.getGroupName());
- }
- }
- }
- }
- List list = timeTaskArgDao.getAutoTask();
- for(TimeTaskArg tta : list) {
- String filePathName = JarUtil.PATH + tta.getJarName();//获取指定到该jar的文件路径
- URLClassLoader loader = JarUtil.loadClassOfJar(filePathName, tta.getClassName()); //动态加载jar中的接口
- if(loader != null){
- JobDetail jobDetail = new JobDetail(tta.getJobName(),tta.getGroupName(),TempleTask.class);
- JobDataMap jm = new JobDataMap();
- jm.put("hessianUrl", tta.getHessianUrl());
- jm.put("methodName", tta.getMethodName());
- jm.put("methodName", tta.getMethodName());
- jm.put("classLoaderName", loader);//每个上传的jar包对应各自的一个classLoader,绑定各自的job
- jobDetail.setJobDataMap(jm);
- CronTrigger cronTrigger = new CronTrigger(tta.getTriggerName(),tta.getGroupName(),tta.getJobName(),tta.getGroupName(),tta.getCronexpression());
- scheduler.scheduleJob(jobDetail,cronTrigger);
- if(!tta.getTriggerStatus().equals("0")) {
- scheduler.pauseTrigger(tta.getTriggerName(), tta.getGroupName());
- }
- }
- }
- } catch (SchedulerException e) {
- e.printStackTrace();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- }
- public Scheduler getScheduler() {
- return scheduler;
- }
- public void setScheduler(Scheduler scheduler) {
- this.scheduler = scheduler;
- }
- public TimeTaskArgDao getTimeTaskArgDao() {
- return timeTaskArgDao;
- }
- public void setTimeTaskArgDao(TimeTaskArgDao timeTaskArgDao) {
- this.timeTaskArgDao = timeTaskArgDao;
- }
- }
Java代码
- import java.net.URLClassLoader;
- import org.quartz.JobDataMap;
- import org.quartz.JobDetail;
- import org.quartz.JobExecutionContext;
- import org.quartz.JobExecutionException;
- import org.springframework.scheduling.quartz.QuartzJobBean;
- import com.caucho.hessian.client.HessianProxyFactory;
- public class TempleTask extends QuartzJobBean {
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
- try {
- HessianProxyFactory factory = new HessianProxyFactory();
- JobDetail jb = context.getJobDetail();
- JobDataMap jm = jb.getJobDataMap();
- String hessianUrl = (String) jm.get("hessianUrl");
- String methodName = (String) jm.get("methodName");
- URLClassLoader loader = (URLClassLoader)jm.get("classLoaderName");//获取加载这个class的loader
- ClassLoader systemClassLoader = Thread.currentThread().getContextClassLoader();//记住系统当前的webAppClassLoader
- Thread.currentThread().setContextClassLoader(loader);//临时设置加载当前任务class的类加载器为当前线程的加载器,否则无法找到类
- factory.setReadTimeout(50000);
- Object o = factory.create(hessianUrl);
- o.getClass().getMethod(methodName).invoke(o,null);
- Thread.currentThread().setContextClassLoader(systemClassLoader);//定时任务执行完之后,重新归还当前线程为系统的线程
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
这个是一个公共的job模板,也就是定时任务执行的时候要真正调用的任务,这个任务没有写在本地,而是用了远程调用,使用的是hessian的代理工厂,根据上传jar包填的hessianURL就可以找到接口,也就是返回的object o,根据方法名通过反射调用业务方法,从而实现执行定时任务
这里要注意的问题是:之前放到jobDataMap中的classLoader这里起重大作用了,因为这个接口class类对象是由自定义的URLClassLoader来加载的,而此时该线程的classLoader为webAppClassLoader,是自定义的URLClassLoader的父加载器,根据全盘委托原则,webAppClassLoader会先向上去寻找这个类,一级一级往上找,没有的话才自己的范围下来找这个类,因为不是它加载的,所以他找不到,会报ClassNotFoundException的错误,此时把该线程的classLoader改为加载这个类的加载器的时候,这个加载器就可以在他自己的范围内找到这个Class,可以顺利的对这个Class对象实例化,从而返回Object对象,让后面可以反射调用(看似简单的代码中往往有很大的学问!)