首页 > 其他分享 >类动态加载

类动态加载

时间:2023-09-19 22:44:51浏览次数:32  
标签:java quot ClassLoader import 动态 class 加载

类动态加载

类加载与反序列化

反序列是利用的readObject()​方法重写,而类加载是为什么 ?

类加载

Java 程序在运行前需要先编译成 class文件​,Java 类初始化的时候会调用 java.lang.ClassLoader​ 加载类字节码,ClassLoader​ 会调用 JVM 的 native 方法(defineClass0/1/2​)来定义一个 java.lang.Class​ 实例

Javac

javac是用于将源码文件.java​编译成对应的字节码文件.class

具体实现步骤 : 源码 -> 词法分析器组件(生成token流)-> 词法分析器组件(语法树)-> 语义分析器组件(注解语法树)-> 代码生成器组件(字节码)

类加载过程

先在方法区找.class​信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成员放在非静态区)静态代码快在类加载时自动执行代码,非静态的不执行,先父类后子类,先静态后飞静态,静态方法和飞静态方法都是被动调用(不调就不执行)

​​​当运行某个类的时候,如果发现这个类还没有被加载,那么就会如上过程进行加载

Java 程序是由 class 文件组成的一个完整的应用程序,在程序运行时,并不会一次性加载所有的 class 文件进入内存,而是通过 Java 的类加载机制(ClassLoader)进行动态加载,从而转换成 java.lang.Class 类的一个实例

动态加载

Java 类加载方式分为 显式​ 和 隐式

  1. 显式​ 即我们通常使用 Java反射​ 或者 ClassLoader​ 来动态加载一个类对象
  2. 隐式​ 指的是 类名.方法名()​ 或 new​ 类实例

显式​ 类加载方式也可以理解为类动态加载,可以自定义类加载器去加载任意的类

Class.forname加载

public class main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("Persion");
        /*
        这是静态代码快
        */
    }
}

我们发现,当使用Class.forName()​调用时,也会进行初始化,可以查看一下底层代码实现

它底层调用了一个forName0方法

它的forName0方法是一个native方法,它是用C或C++实现的,但是上方还存在一个forName的重载方法,它就可以判断是否需要初始化(存在三个参数,第一个是类,第二个是否初始化,第三个是类加载器)

public class main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c = Class.forName("Persion", false, ClassLoader.getSystemClassLoader()); // 此时就不会在初始化了 这里使用的是系统类加载器
		// c.newInstance(); // 这是初始化操作
    }
}

ClassLoader

通过ClassLoader​来实现类加载

public class main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class<?> aClass = cl.loadClass("Persion");
        aClass.newInstance();
    }
}

这里通过aClass.newInstance()​​来实现类初始化,那么可以考虑的一个问题是能否通过在别的地方写的一些类,然后通过ClassLoader来加载,那么这就是可以执行任意类(比反射实现的效果还要多,反射在一些私有的是不允许调用的)

类加载器的种类

Bootstarp ClassLoader

启动类加载器 : 这个类加载器负责将一些核心的、被JVM识别的类加载进来,用C++实现,与JVM一体的**,**用于加载一些java.lang 目录下的是无法直接获取到的

Extension ClassLoader

扩展类加载器 : 这个类加载器用来加载 Java 的扩展库,其目录在jre\lib\ext

Application ClassLoader

App类加载器/系统类加载器 : 用于加载当前目录下我们自己定义编写的类

User ClassLoader

用户自己实现的加载器 : 当实际需要自己掌控类加载过程时才会用到,一般没有用到

双亲委托机制

如果一个类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需要的类)子加载器才会自己去加载

  1. 首先,检查请求的类是否已经加载过了
  2. 未加载,则请求父类加载器去加载对应路径下的类
  3. 如果加载不到,才由下面的子类依次去加载

java.lang.String -> 本地加载器 -> 扩展加载器 -> 根(顶层)加载器

类加载机制

  1. 在调用loadClass处打入断点

可以看到它的默认类加载器是AppClassLoader

  1. 由于传入的是一个参数,它会调用到本应该调用的是Launcher类中的loadClass方法,但是它斌没有接收一个参数的loadClass方法,就会去调用父类的loadClass方法

  1. ClassLoader​中的loadClass(String name)​会返回一个两个参数的loadClass方法

  1. 之后将两个参数的返回给子类(Launcher类)

  1. 子类执行完成后,又会调用父类的loadClass方法

​​

这里就相当于是执行了双亲委派的流程

先会判断是否是ExtClassLoader​的父属性,如果是的话就回去指定它的loadClass方法,很明显这里是会进入ExtClassLoader​类中执行它的loadClass

但是通过搜索会发现它并不存在loadClass方法,去父类找URLClassLoader​中找,它又会去调用它的父类loadClass方法

又回到了ClassLoader.loadClass()

但是此时的加载类已经变成了ExtClassLoader

  1. 继续往下执行,当它再次判断时,由于它类加载的父属性是BootstropClassLoader​,它是由C编写的,在java中是null,所以不会在进入执行else

之后又会去findBootstrapClassOrNull()中寻找,还是为false

  1. 之后继续执行findClass(),而它是一个重写的方法,正常在子类中执行

而当前的加载类还是ExtClassLoader​,但是它也没有该方法,只能去它对应的父类URLClassLoader​中找

​​

因为这里使用的是ExtClassLoader​,它是用来加载java的扩展库,所以这里是找不到的,为null,到此ExtClassLoader​执行完成,退回ApplicationClassLoader

  1. 程序退回到ClassLoader.loadClass()判断处,继续往下执行

这里就是以ApplicationClassLoader​身份去执行findClass

  1. 由于ApplicationClassLoader​ 用于加载我们自己定义编写的类,所以这里肯定是可以找到的

​ucp是一个URLClassPath类,通过它在当前的ClassLoader加载路径中找

  1. 通过upc.getResource()​找到之后会再去调用一个defindClass()

​​image​​

URLClassLoader​类重写了defineClass()​最终还是调用了一个多参数的defindClass()

  1. 继续执行查看,发现会调到SecureClassLoader​类中,而它是URLClassLoader​的父类

它也返回了一个调用defineClass()

  1. 继续追踪,查看其最终落脚

最终在ClassLoader​中的defineClass​停止,它又执行了一个defineClass1()​,它的definClass()方法中可以看到存在5个参数,第一个是类名、第二个参数是字节码、第三个参数是起始位置、第四个是长度、第五个是保护域

defineClass1​是一个native类型,最终通过它来完成整个类的加载

任意类加载

ClassLoader.defineClass

import main.Test;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;

public class classLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader cl = ClassLoader.getSystemClassLoader();

    Method defineClassLoader = ClassLoader.class.getDeclaredMethod(&quot;defineClass&quot;, String.class, byte[].class, int.class, int.class); // 反射获取defineClass方法
    defineClassLoader.setAccessible(true); // 关闭安全检测
    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;)); // 将class转为字节
    Class c = (Class) defineClassLoader.invoke(cl, &quot;ss&quot;, code, 0, code.length); // 加载类
    c.newInstance(); // 创建类
}

}

URLClassLoader

可使用jar/file/http​协议进行加载类

import main.Test;

import java.net.URL;
import java.net.URLClassLoader;

public class classLoaderTest {
public static void main(String[] args) throws Exception {
URLClassLoader file = new URLClassLoader(new URL[]{new URL("file:///H:\tmp\")}); // 通过file协议
Class<?> aClass = file.loadClass("ss");
aClass.newInstance();

	// 使用file协议
    URLClassLoader file = new URLClassLoader(new URL[]{new URL(&quot;file:///H:\\tmp\\&quot;)}); // 通过file协议
    Class&lt;?&gt; aClass = file.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	// 使用http协议
    URLClassLoader http = new URLClassLoader(new URL[]{new URL(&quot;http://localhost:80/&quot;)});
    Class&lt;?&gt; aClass = http.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	//使用jar协议

}

}

Unsafe.defineClass

import main.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;

public class classLoaderTest {
public static void main(String[] args) throws Exception {

    ClassLoader cl = ClassLoader.getSystemClassLoader();

    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;));

    Class c = Unsafe.class;
    Field theUnsafe = c.getDeclaredField(&quot;theUnsafe&quot;);
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    Class c2 = (Class) unsafe.defineClass(&quot;ss&quot;, code, 0, code.length,cl,null);
    c2.newInstance();
}

}

标签:java,quot,ClassLoader,import,动态,class,加载
From: https://www.cnblogs.com/yuxingjiu/p/dynamic-load-2moceb.html

相关文章

  • Linux多个动态库间的符号冲突问题
    背景今天遇到一个奇怪的问题,在客户车机上客户传入json字符串,使用cjson库cJSON_Parse()函数是成功的,但是通过cJSON_GetObjectItem()获取属性却失败了,代码如下gtc_nlu_product_t*get_product_config(constchar*str,gtc_pool_t*pool){intret;......
  • python使用sql批量插入数据+查看执行的语句+动态sql创建表+动态创建索引
    classTest():cursor=connection.cursor()data_to_insert=[]sql="INSERTINTOtest_t(id,name)VALUES""""(%s,%s)"""d=('1',"apple")data_to_insert.append(d)......
  • 20230919_京东良西延动态
    京良路西延近况4京良路西段工程是咱房山居民十分关心的一件大事该工程横跨房山区、丰台区道路全长约3730米近日,京良路西段涉及到的铁匠营村腾退搬迁又有新进展了村中道路两侧安装了施工围挡又新张挂了很多宣传条幅显示项目进入收尾阶段腾退搬迁工作将加快实施青......
  • 【Postman】动态变量(也称Mock函数)
    Postman使用faker库来生成样本数据,包括随机姓名、地址、电子邮件地址等等。您可以多次使用这些预定义变量来为每个请求返回不同的值。您可以像使用Postman中的任何其他变量一样使用这些变量。它们的值是在执行时生成的,它们的名称以guidor$timestamp。以下是在请求/收集运行期......
  • 服务器上加载数据的问题
    为了加载数据需要联网在服务器上报错猜测是因为服务器进行了端口映射捋着找到utils.py修改运行还是一样的报错—发现修改的文件好像在本地不在服务器上,不知道怎么/能够deployment最初或者始终只是为了解决datasetdownload的报错......
  • [代码随想录]Day48-动态规划part16
    题目:583.两个字符串的删除操作思路:还是最长公共子序列,假设最长公共子序列长度是l;那么需要删除的次数是len(s1)-l+len(s2)-l代码:funcminDistance(word1string,word2string)int{lens1:=len(word1)lens2:=len(word2)dp:=make([][]int,lens1+......
  • 动态面板案例分析
    动态面板模型分析如果在面板模型中,解释变量包括被解释变量的滞后值,此时则称之为“动态面板模型”,其目的是处理内生性问题。动态面板模型发展分为3个阶段,第1阶段是由ArellanoandBond(1991)提出的差分GMM(differenceGMM),第2阶段由ArellanoandBover(1995)提出水平GMM,第3阶段是B......
  • Vue多层级组件传递动态具名插槽
    这里以一个table组件的二次包装为案例,父组件中使用子组件(table组件)再次包装:Vue2:子组件,inTable<template><table><thead><tr><thv-for="(item,index)ofcolumns":key="index">{{item.t......
  • 动态内存分配(callco,realloc,笔试题目)2
    相比于malloc加了有一个自动初始化的功能intmain(){ int*p=(int*)calloc(10,sizeof(int));//创建之后就默认数据初始化为0 if(p==NULL) { printf("%s\n",strerror(errno)); } else { inti=0; for(i=0;i<10;i++) { *(p+i)=i; } for(i......
  • [MAUI]实现动态拖拽排序列表
    @目录创建页面元素创建可绑定对象创建绑定服务类拖拽(Drag)拖拽悬停,经过(DragOver)释放(Drop)限流(Throttle)和防抖(Debounce)项目地址上一章我们使用拖放(drag-drop)手势识别实现了可拖拽排序列表,对于列表中的条目,完整的拖拽排序过程是:手指触碰条目->拖拽条目->拖拽悬停在另一个......