Tomcat源码分析--热部署原理
热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。tomcat支持当你对这个文件进行修改时,会重新把这个新的文件加载到JVM中。当然这个功能是需要我们进行配置的。
我们可以在server.xml 中的 Host标签下配置一个Context标签,这里的reloadable="true",就表示是否重新加载,即我们所说的热部署。
<Context path="jsp-web" reloadable="true" docBase="D:\workspace-neno\jsp-web\WebContent"> <!-- DevLoader 加载指定位置的jar包及编译后的classes文件夹所有内容 --> <Loader className="org.apache.catalina.loader.DevLoader" reloadable="true" useSystemClassLoaderAsParent="false" /> </Context>
先不说其实现原理,先来一段示例:
一、示例
1.新建一个项目名称为demo1
添加一个测试类和一个依赖jar
public class PrintTest { /** * 打印测试 */ public void print() { List<String> list = new ArrayList<String>(); // 第三方依赖jar,如果jar包没有加载进来,或者是 com.alibaba.fastjson.JSONArray 没有加载会抛出异常 JSONArray arr = new JSONArray(); // 用来打印测试输出的 System.out.println("111fdasfd33311"); } }
2.新建一个项目名称为demo2
(1)自定义一个类加载器
package com.test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URLClassLoader; /** * 自定义类加载器:加载自定义的类文件 */ public class MyClassLoader extends ClassLoader { /** * 这里CLASS_PATH 使用的是demo1项目路径 */ static final String CLASS_PATH = "D:\\workspace-neno\\demo1\\bin"; URLClassLoader loader = null; public MyClassLoader(URLClassLoader loader) { this(); this.loader = loader; } public MyClassLoader() { super(ClassLoader.getSystemClassLoader()); } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { File file = new File(CLASS_PATH + "/" + className.replace(".", "/") + ".class"); if (file.exists()) { try { FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bout = new ByteArrayOutputStream(); int b = 0; while ((b = fis.read()) != -1) { bout.write(b); } fis.close(); byte[] _byte = bout.toByteArray(); return super.defineClass(className, _byte, 0, _byte.length); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { // 如果文件不存在,说明是jar包中的类,因此使用 loader 来加载这个类 return loader.loadClass(className); } return null; } }
(2)自定义一个URLClassLoader类
package com.test; import java.net.URL; import java.net.URLClassLoader; /** * 继承 URLClassLoader,目录是为了重写一个addURL方法 * */ public class MyURLClassLoader extends URLClassLoader { public MyURLClassLoader(URL[] urls) { super(urls); } @Override protected void addURL(URL url) { super.addURL(url); } }
(3)添加一个测试类
package com.test; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; public class LoadJarsAndClassesTest { public static MyURLClassLoader loader = null; // ../classes 或bin目录下所有文件的目录 public static String CLASSES_PATH = "D:\\workspace-neno\\demo1\\bin"; // jar文件存放的目录 public static String JARS_PATH = "D:\\workspace-neno\\demo1\\lib"; public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { LoadJarsAndClassesTest test = new LoadJarsAndClassesTest(); // 加载jar文件 test.loadJarsFile();
// 一直打印输出,测试修改代码时,输出的内容是否发生变化 while (true) { // 加载类文件 test.loadClassesFile(); test.printTest(); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 1.加载其它项目下自定义的类文件 2.并实例化 3.调用其它项目下自定义类文件下的方法 */ private void printTest() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException { MyClassLoader myClassLoader = new MyClassLoader(loader); Class<?> clazz = myClassLoader.findClass("com.print.PrintTest"); Object object = clazz.newInstance(); Method method = clazz.getDeclaredMethod("print", null); method.invoke(object, null); } /** * 通过 URLClassLoader 加载.class所在的根目录 */ private void loadClassesFile() { File classesDir = new File(CLASSES_PATH); if (classesDir.exists()) { try { URL url = classesDir.toURI().toURL(); loader.addURL(url); } catch (MalformedURLException e) { e.printStackTrace(); } } } /** * 通过 URLClassLoader 加载lib目录下所有的jar文件 */ private void loadJarsFile() { File jarsDir = new File(JARS_PATH); if (jarsDir.exists()) { try { File[] jarFiles = jarsDir.listFiles(); URL[] urls = new URL[jarFiles.length]; int i = 0; for (File file : jarFiles) { URL url = file.toURI().toURL(); urls[i++] = url; } loader = new MyURLClassLoader(urls); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
(4)启动测试,修改demo1项目中的PrintTest 中print方法中的输出内容
总结:
(1)MyClassLoader 这个类主要是用来加载自定义类.class文件,这里指的就是PrintTest.class文件;
(2)URLClassLoader 这个类主要是用来加载jar文件 及 bin 或者 classes 下所有的文件包括.class文件及.xml文件等;
(3)Tomcat 热部署其原理和我的这个示例是一样的。Tomcat启用了一个监听器线程,这个线程其实也是一个循环监听,tomcat每10秒监听一次文件是否修改,如果修改了,则通过重新加载.class文件、xml文件等,就相当于将我这里的loadClassesFile 方法重新执行一次。
二、源码分析
未完成,待续...