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 方法重新执行一次。

 

 

二、源码分析

未完成,待续...

posted @ 2020-03-19 19:51  cao_xiaobo  阅读(766)  评论(0编辑  收藏  举报