今天在修改项目一个JSP文件时,突然想到Tomat是怎么实现动态实时加载JSP编译后的class类的?
查了半天资料,看了很多文章,终于明白是怎么回事了:ClassLoader,当tomcat发现jsp改变后,将用新的ClassLoader去加载新的类
具体原理我将单独总结一下,这里简单实现了动态加载类
1.定义服务类
public class Servlet { public void service(){ System.out.println("运行服务方法"); } }
2.定义服务线程
public class ServiceThread extends Thread{ public void run(){ try { ClassLoader classLoader = this.getContextClassLoader(); Class clazz = classLoader.loadClass("Servlet"); Method service = clazz.getMethod("service", null); service.invoke(clazz.newInstance(), null); } catch (Exception e) { e.printStackTrace(); } } }
3.自定义ClassLoader
public class MyClassLoader extends ClassLoader{ @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ try { // 我们要创建的Class对象 Class clasz = null; // 必需的步骤1:如果类已经在系统缓冲之中 // 我们不必再次装入它 clasz = findLoadedClass(name); if (clasz != null) return clasz; try { // 读取经过加密的类文件 if(name.equals("Servlet")){
//加载class文件字节 byte classData[] = Util.readFile(ProgramPathHelper.getProgramPath()+"/"+name + ".class"); if (classData != null) { // ... 再把它转换成一个类 clasz = defineClass(name, classData, 0, classData.length); } } } catch (Exception e) { e.printStackTrace(); } // 必需的步骤2:如果上面没有成功 // 我们尝试用默认的ClassLoader装入它 if (clasz == null) clasz = findSystemClass(name); // 必需的步骤3:如有必要,则装入相关的类 if (resolve && clasz != null) resolveClass(clasz); // 把类返回给调用者 return clasz; } catch (Exception ie) { throw new ClassNotFoundException(ie.toString()); } } }
4.实现文件监听类
public class CCFileListener implements FileAlterationListener{ public static HashMap<String,ClassLoader> claMap = new HashMap<String, ClassLoader>(); ZJPFileMonitor monitor = null; @Override public void onStart(FileAlterationObserver observer) { //System.out.println("onStart"); } @Override public void onDirectoryCreate(File directory) { System.out.println("onDirectoryCreate:" + directory.getName()); } @Override public void onDirectoryChange(File directory) { System.out.println("onDirectoryChange:" + directory.getName()); } @Override public void onDirectoryDelete(File directory) { System.out.println("onDirectoryDelete:" + directory.getName()); } @Override public void onFileCreate(File file) { System.out.println("onFileCreate:" + file.getName()); dyncLoadClass(file.getName()); } @Override public void onFileChange(File file) {
//文件改变处理函数 System.out.println("onFileChange : " + file.getName()); dyncLoadClass(file.getName()); } private void dyncLoadClass(String className){ if(className.contains("Servlet")){ // ZJPFileMonitor.thread.setContextClassLoader(new MyClassLoader()); claMap.put(className, new MyClassLoader()); } } @Override public void onFileDelete(File file) { System.out.println("onFileDelete :" + file.getName()); } @Override public void onStop(FileAlterationObserver observer) { //System.out.println("onStop"); } }
public class CCFileMonitor { FileAlterationMonitor monitor = null; public CCFileMonitor(long interval) throws Exception { monitor = new FileAlterationMonitor(interval); } public void monitor(String path, FileAlterationListener listener) { FileAlterationObserver observer = new FileAlterationObserver(new File(path)); monitor.addObserver(observer); observer.addListener(listener); } public void stop() throws Exception{ monitor.stop(); } public void start() throws Exception { monitor.start(); } public static void main(String[] args) throws Exception { CCFileMonitor m = new CCFileMonitor(5000); m.monitor(ProgramPathHelper.getProgramPath(),new CCFileListener()); m.start(); Servlet servlet = new Servlet(); servlet.service(); MyClassLoader defaultCl = new MyClassLoader(); while(true){ Thread.currentThread().sleep(3000); Thread t = new ServiceThread(); //设置新线程的类加载器 ClassLoader classLoader = CCFileListener.claMap.get("Servlet.class"); if(classLoader==null){ classLoader = defaultCl; } t.setContextClassLoader(classLoader); t.start(); } } }
public static HashMap<String,ClassLoader> claMap = new HashMap<String, ClassLoader>();
在监听到文件改变后,依据类名重new一个类加载器,用于加载类。
ClassLoader classLoader = CCFileListener.claMap.get("Servlet.class");
if(classLoader==null){
classLoader = defaultCl;
}
首先获取类名对应的加载器,如果没有使用默认的加载器
ClassLoader classLoader = this.getContextClassLoader();
Class clazz = classLoader.loadClass("Servlet");
Method service = clazz.getMethod("service", null);
service.invoke(clazz.newInstance(), null);
在线程内部使用刚才在外部设置的线程上下文加载器加载新的Servlet,并执行