热部署定时任务管理实现

  • Post author:
  • Post category:其他


热部署定时任务管理实现(上传jar+动态加载jar中接口类文件+hessian远程调用执行业务)

1.新增定时任务

主要是配置定时任务的执行周期,涉及到热部署的就是关于接口jar上传,要调用的远程接口类名称,接口中要被调用的方法名称、远程调用的hessianURL,这样子就配置好了一个定时任务,信息存放到数据库,这样就成功添加了一个定时任务

用到了loadJar这个方法,这里用到了动态加载,刚上传的jar文件要被动态的加载到JVM中,使用了URLClassLoader根据类名称读取jar中指定的类,并且返回加载相应jar的这个URLClassLoader,因为后面用到这个加载的类的时候,必须要用加载它的类加载器才行,否则会报ClassNotFoundException

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

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;
    }
}

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对象,让后面可以反射调用(看似简单的代码中往往有很大的学问!)



版权声明:本文为hantiwenti原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。