首页 > 其他分享 >热部署学习

热部署学习

时间:2023-03-20 11:34:05浏览次数:29  
标签:自定义 部署 class 学习 对象 实例 Class 加载

概述

热部署对于我们这种开发同学来平不陌生吧,比如在IDEA修改一行代码,会自动热部署,并不需要重启,市面上热部署的框架有很多:Jrebel等。今天我就来学习一下热部署。 

原理

我们知道,java程序编译后会生成class文件,在运行时由类加载器对class文件进行加载生成Calss对象,用于创建实例,而实例会执行我们具体的代码逻辑。那么我们似乎可以想象一种实现热部署的方式,【在系统运行的时候,替换class文件,并且让jvm重新加载该class文件,并将实例化的操作由新的Class操作】,这句话看似容易但是有三个关键性的问题要解决:

  • 如何检测到class文件变更
  • 如何让类加载器重新加载calss文件
  • 如何让对新类的实例化采用新的Class

 

1、对于第一个问题,目前java也很好的支持对于文件的监听:比如commons-io包的FileAlterationListenerAdaptorl类。

2、对于第二个问题,首先要明白Java类是通过JVM加载的,其加载模式为双亲委派模型,如图:

在进行类加载时,首先会自底向上挨个检查是否已经加载了指定类,如果已经加载则直接返回该类的引用。如果到最高层也没有加载过指定类,那么会自顶向下挨个尝试加载,直到用户自定义类加载器,如果还不能成功,就会抛出异常。这里要明确的是一个Calss类是由它的类加载器和它的全限定名唯一确定的,一个类加载器只能加载一个同名类,每个类加载器内部会维护一个namespace命名空间,记录该类加载器已经加载的类,同时在Java默认的类加载器层面作判断,如果已经有了该类,则不再重复加载,如果强行绕过判断并使用自定义类加载器重复加载,JVM 将会抛出 LinkageError:attempted duplicate class definition for name。因此想要重新加载一个class文件生成Class对象,要么我们改变类的命名,比如加上版本号,要么就是使用不同的类加载器去加载。根据双亲委派模型,显然我们不能使用相同的父类加载器去加载同名的类,这就需要实现自定义的类加载器,并且每次要使用新的类加载器对象去加载类生成实例对象。

在此我们看下实现一个自定义类加载器需要实现的关键方法,ClassLoader的loadClass方法:

  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //检查该class是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果没有被加载过交给父类加载器去加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

 

这样解决第二个问题就变得简单了,我们只需要继承ClassLoader并重写loadClass方法实现自定义类加载器,每次使用新的类加载对象按照自定义的类加载方法去加载class文件即可。

3、如何让接下来对修改后的类的实例调用都是用新的Class对象生成,试着分析一下,首先旧的类加载器和Class对象不能再继续使用,我们可以借助jvm的垃圾回收机制释放掉这部分引用,我们要保证每次实例化对象时候使用新生成的Class对象。

【实例化对象】的含义:

在Java中,类的实例化流程分为两个部分:类的加载和类的实例化。
类的加载又分为显式加载和隐式加载,大家使用new关键字创建类实例时,其实就隐式地包含了类的加载过程。
对于类的显式加载来说,比较常见的是使用Class.forName。
其实,它们都是通过调用ClassLoader类的loadClass方法来完成类的实际加载工作的。
直接调用 ClassLoader 的 loadClass 方法是另外一种不常用的显式加载类的技术。

因此要想保证每次实例化对象时候使用新生成的Class对象,最简单的思路就是控制对象实例化的操作,避免使用new的方式去实例化对象,这个想法是不是很熟悉?没错,就是Spring的思路,我们要将控制反转自己控制对象实例化的操作,并维护实例化后的对象引用,参考Spring,我们需要有一个【容器】去维护所有的实例化对象,这样我们使用自己的类加载器加载完新的class文件生成Class对象后,调用newInstance()方法实例化后将其使用【容器】维护起来,系统内所有的调用都使用【容器】内的对象引用即可。

 

总结一下:

  • 如何检测到class文件变更 --> 使用java本身的能力监听文件夹变化
  • 如何让类加载器重新加载calss文件 --> 监听到文件变化后,使用自定义的类加载器去加载class文件
  • 如何让对新类的实例化采用新的Class --> 使用新加载的Class对象生成实例化引用,并自己维护起来,使接下来的调用使用新的实例化对象。

 

注意:上述实现只是热部署的一种实现方式,每一步都可以有不同的实现,比如第一步,监听的文件不一定是class文件,任何形式的二进制字节流都可以,比如可以是JAR、EAR、WAR;Applet;JSP;groovy脚本等,这是虚拟机提供的能力。比如第二步,自定义类加载的实现方法也是千千万万种,第三步的方式也有多种,只要能保证上述效果即可。

标签:自定义,部署,class,学习,对象,实例,Class,加载
From: https://www.cnblogs.com/xiaofeng-fu/p/17235736.html

相关文章

  • 学习下开发 WebAssembly
    在过去的几十年里,Web浏览器作为最流行的跨平台应用经久不衰。从另一个角度看浏览器,它是最受欢迎的应用交付平台之一。想想你使用的所有网站,它们取代了你过去用桌面上运行的......
  • 使用K8S进行蓝绿部署的简明实操指南
    在之前的应用部署系列文章里,我们已经介绍过什么是蓝绿部署。如需回顾,点击下方文章链接即可重温。本文我们将会介绍如何使用Kubernetes实现蓝绿部署。 应用部署初探:3......
  • 使用Docker部署Consul集群并由Ocelot调用
    关于consul的介绍就不写了百度就行,我们直接开干。一、部署consul集群拉取consul的镜像dockerpullconsul然后部署consul容器dockerrun--nameconsul1-d-p85......
  • Nginx服务部署及基础配置
    一、nginx1、Nginx功能介绍静态的web资源服务器html,图片,js,css,txt等静态资源http/https协议的反向代理7层结合FastCGI/uWSGI/SCGI等协议反向代理动态资源请求......
  • Zabbix6.0部署全过程
    1.踩坑说明老胡让我帮他部署个CentOS8的Zabbix6,我说这还不是洒洒水的事儿嘛,无奈打脸来得太快。网络的坑:移动网络,打死都不可达;系统的坑:CentOS8Stream是什么鬼;开源的坑:官......
  • Kubernetes安装篇(下):基于Kubeadm方式的集群部署
    Kubernetes安装篇(下):基于Kubeadm方式的集群部署2022-05-22 346举报简介: 在实际生产环境中,Kubernetes环境就比这复杂的多,起码也是集群起步,因此,本文将从生产环境出发,为你......
  • 使用kubernetes 官网工具kubeadm部署kubernetes(使用阿里云镜像)
    使用kubernetes官网工具kubeadm部署kubernetes(使用阿里云镜像)  系列目录kubernetes简介Kubernetes节点架构图:kubernetes组件架构图:准备基础环境我们将......
  • 部署双主高可用K8S集群
    部署双主高可用K8S集群原创 王思宇 收录于合集#kubernetes8个#服务器9个#运维14个#容器5个#docker5个高可用原理:利用keepalived的vrrp协议创建vip,kubeadm......
  • go语言学习-标准库flag和log
    Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单。os.Args获取命令行参数个数packagemainimport("fmt""os")//os.Args//获取命令行参......
  • go语言学习-标准库IO和Strconv
    IO输入输出的底层原理终端其实是一个文件,相关实例如下:os.Stdin:标准输入的文件实例,类型为*Fileos.Stdout:标准输出的文件实例,类型为*Fileos.Stderr:标准错误输出的文件实例,类型......