慕课网Spring Boot热部署第一讲:java类热加载案例

1、热部署使用场景
    
    本地调试
    线上发布
    
    全年不间断运行,重发布程序后不重启项目
    
    本地线上都适用
    无需重启服务器
        提高开发、调试效率
        提升发布、运维效率、降低运维成本

    前置知识:java、Spring、构建Spring Boot

    提纲:热部署原理解析、案例分析、项目演示、测试、发布程序、总结

2、热部署和热加载的联系和区别
    联系:  
        不重启服务器编译、部署项目
        基于java的类加载器实现
    区别:    
        部署方式:热部署在服务器运行时部署项目
              热加载在运行时重新加载class,只加载修改后的类编译字节class
        实现原理:热部署直接重新加载整个应用
              热加载在运行时重新加载class
        使用场景:热部署更多是在生产环境中使用
              热加载更多是在开发环境中使用:难以监控class的监控

3、热部署原理解析
    java类的加载过程
        初始化JVM、产生启动类加载器、标准控制类加载器(子类自动加载)、系统加载器(子类自动加载)
        加载class文件(交给其父类加载)
    类加载的五个阶段
        加载:找到java的静态存储结构并加载到虚拟机内,转换为方法区运行时使用的数据结构,生成class对象,用户可以自定义类加载器参与进来
        验证:确保字节码是安全的,确保不会对虚拟机造成危害,可以通过虚拟机的启动参数来禁用一些验证(不推荐)
        准备:确定内存布局,初始化类变量,是赋初始值(例如:静态变量等赋为0),不会执行方法中赋值语句
        解析:将符号引用变为直接引用
        初始化:调用程序自定义代码(例如:静态变量真正赋值为12)
            5种情况必须初始化
            a.遇到new,get, static这几条字节码指令,如果类没有初始化,则需要触发初始化。final修饰的类会在编译时把结果放到常量池中,即使调用也不会触发初始化。final关键字它修饰的是常量。
            b.使用反射对类进行反射调用,如果类没有进行初始化,就需要先初始化
            c.当初始化一个类的时候,如果发现其父类还没有进行过初始化,需要先触发父类的初始化。先初始化父类,在初始化子类。
            d.虚拟机启动的时候用户需要制定一个要执行的主类,虚拟机会先初始化这个主类。
            e.使用jdk1.7动态机制相关的句柄会进行初始化。
    java类加载器特点
        a、由AppClass Loader(系统类加载器)开始加载指定的类
        b、类加载器将加载任务交给其父、如果其父找不到,再由自己去加载
        c、bootStrap Loader(启动类加载器)是最顶级级的类加载器

    java类的热部署方式
        a、通过类的热加载实现:继承java.lang.classLoad类,复写
            protected Class<?> findClass(String name) throws ClassNotFoundException{
                System.out.println("加载类==="+name);
                byte[] data =  loadClassData(name);
                return this.defineClass(name,data,0,data.length);
            }
        b、配置Tomcat
            1)把项目web文件夹放到webapps里
            2)tomcat\cof\server.xml中的 <host></host>内部添加<context/>标签
            3)%tomcat_home%\conf\Catalina\localhost中添加一个xml,该xml名字作为访问路径的一部分,其实就是Context标签的内容

4、java类热加载案例分析
    写一个java类热加载的时间案例
    要求:类层次结构清晰,修个java类文件不要重启服务或者重新编译运行程序,适当用一下设计模式(工厂模式)
    
    java虚拟机实现热加载的原理是项目运行过程中虚拟机后台会启动一条线程监控类的时间戳,如果某个类的时间戳发生变化,这个类就会被虚拟机进行热加载。    

    
4.1、核心类MyClassLoader :继承ClassLoader实现Java类的热加载
    复写findClass方法
        loadClassData:读取class文件
        defineClass:将符合class文件格式的字节数组加载到内存当中

package com.imooc.classloader;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

/*
 * 自定义Java类加载器实现Java类的热加载
 */
class MyClassLoader extends ClassLoader{
    
    //要加载的Java类的classpath路径
    private String classpath;
    
    public MyClassLoader(String classpath) {
        super(ClassLoader.getSystemClassLoader());
        this.classpath = classpath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        
        byte[] data = this.loadClassData(name);
        //defineClass是将符合class文件格式的字节数组加载到内存当中,从而生成class对象。也可以从本地文件中获取,你只要提供符合class格式的字节数组,它就能正确的生成class对象
        return super.defineClass(name, data, 0, data.length);
    }

    
    /*
     * 加载class文件中的内容
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        try {
            //将包名中的点转为/
            name = name.replace(".", "//");
            //将class文件读到文件输入流中
            FileInputStream is = new FileInputStream(new File(classpath + name + ".class"));
            //字节输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            //读文件
            while((b = is.read())!=-1){
                baos.write(b);
            }
            is.close();
            return baos.toByteArray();
        
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
}



4.2、接口BaseManager:实现这种接口的子类需要动态更新,动态加载

package com.imooc.classloader;

/**
 * 实现这种接口的子类需要动态更新,动态加载
 * @author mayl
 *
 */
public interface BaseManager {
    public void logic();
}


4.3、实现类Mymanager:实现BaseManager接口,该类改变会有动态加载

package com.imooc.classloader;

/**
 * BaserManager的子类,此类需要实现java类的热加载功能
 * @author mayl
 *
 */
public class MyManager implements BaseManager {

    @Override
    public void logic() {
        System.out.println("我在慕课网学习Java类的热加载案例修改后实时更新不用重新运行");
    }

}


4.4、LoadInfo:封装加载类的信息
    自定义的类加载、加载时间戳、BaseManager

package com.imooc.classloader;

/**
 * 封装加载类的信息
 * @author mayl
 *
 */
public class LoadInfo {

    //自定义的类加载
    private MyClassLoader myClassLoader;
    //记录要加载的类的时间戳
    private long loadTime;
    private BaseManager baseManager;
    
    public LoadInfo(MyClassLoader myClassLoader, long loadTime) {
        super();
        this.myClassLoader = myClassLoader;
        this.loadTime = loadTime;
    }
    //...get set
    
}


4.5、工厂类ManagerFactory:加载manager的工厂
    LoadInfo 的 map 、要加载类的路径
    
    getManager()
        读取class文件,对于不存在和时间戳改变的重新加载
    load()
        创建类加载器loadClass通过findClass找到loadClass,(反射机制创建BaseManager子类对象)放到map中

package com.imooc.classloader;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * 加载manager的工厂
 * @author mayl
 *
 */
public class ManagerFactory {
    //记录热加载类加载信息
    private static final Map<String,LoadInfo> loadTimeMap
        = new HashMap<String,LoadInfo>();
    //要加载的类的classpath
    public static final String CLASS_PATH = "G:/myeclipseWork/Myclassloader/bin/";
    //实现热加载的类的全名称 (包名加类名)
    public static final String MY_MANAGER = "com.imooc.classloader.MyManager";

    public static BaseManager getManager(String className){
        File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/")+".class");
        //取得最后一次修改时间
        long lastModified = loadFile.lastModified();
        //loadTimeMao不包含className为key的LoadInfo信息,证明这个类没有被加载,那么需要加载这个类到JVM
        if(loadTimeMap.get(className)==null){
            load(className,lastModified);
        }//加载类的时间戳变化了,我们同样要重新加载这个类到JVM
        else if(loadTimeMap.get(className).getLoadTime() != lastModified){
            load(className,lastModified);
        }
        
        return loadTimeMap.get(className).getBaseManager();
    }

    private static void load(String className, long lastModified) {
        MyClassLoader myClassLoader = new MyClassLoader(CLASS_PATH);
        Class<?> loadClass = null;
        try {
            loadClass = myClassLoader.findClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        BaseManager manager = newInstance(loadClass);
        LoadInfo loadInfo = new LoadInfo(myClassLoader, lastModified);
        loadInfo.setBaseManager(manager);
        loadTimeMap.put(className, loadInfo);
    }

    //以反射的方式创建BaseManager子类对象
    private static BaseManager newInstance(Class<?> loadClass) {
        try {
            return (BaseManager)loadClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("null");
        return null;
    }
}


4.6、多线程类监听类

package com.imooc.classloader;


/**
 * 后台启动一条线程不断刷新重新加载实现热加载的类
 * @author mayl
 *
 */
public class MsgHandler implements Runnable {

    @Override
    public void run() {
        while(true){
            BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER);
            
            manager.logic();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}


4.7、测试类启动线程

package com.imooc.classloader;

/**
 * 测试java类的热加载
 * @author mayl
 *
 */
public class ClassLoaderTest {
    public static void main(String[] args) {
        new Thread(new MsgHandler()).start();
    }
}

最后运行测试类,改变MyManager实时改变运行结果
其实就是在class文件不存在或时间戳改变后重新加载class文件到内存中,只是用了工厂模式和反射机制使代码简洁
        
    



猜你喜欢

转载自blog.csdn.net/qq_20367813/article/details/79149091