Java是依赖JVM实现的跨平台开发,程序运行前需要先编译class文件,
Java类初始化的时候会调用java.lang.Classloader来加载字节码,
然后ClasssLoader调用JVM的native方法来定义一个java.lang.Class实例。
public class TestHello { public String hello(){ return "hello,world!"; } }
这里编译成一个java文件
使用javap -c 命令反汇编class文件
JVM再执行我们的TestHello时候会先解析class的二进制内容,其实就是javap命令生成的字节码。
所有的java类都必须经过JVM加载后才能运行,ClassLoader主要作用就是用于Java类文件的加载。
在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)
、Extension ClassLoader(扩展类加载器)(接触较少)
、App ClassLoader(系统类加载器)(直接加载我们的代码)
,AppClassLoader
是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader
加载类。
可以这么说 用java.io.File.class.getClassLoader()取得是null的对象,就是用Bootstrap去加载的,以为它是用C++去实现的,所以当然得不到对应的对象了!
ClassLoader
类有如下核心方法:
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
Java的加载方式有显式与隐式。
显式:Java反射或者ClassLoader来动态加载一个类对象。
隐式:类名.方法名()或者new()类的实例。
我们可以自定义类加载器去加载任意的类
// 反射加载TestHelloWorld示例 Class.forName("com.anbai.sec.classloader.TestHelloWorld"); // ClassLoader加载TestHelloWorld示例 this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");
5.重写classloader
可以通过重写classloader类来加载字节码(为一个不存在的类),加载到JVM里面去,然后通过反射去调用这个类来实例化他的对象调用他的方法。这里就以TestHelloWorld类为例,先注释掉之前写的TestHelloWorld,
完整重写代码
package com.anbai.sec.classloader; import java.lang.reflect.Method; /** * Creator: yz * Date: 2019/12/17 */ public class TestClassLoader extends ClassLoader { // TestHelloWorld类名 public static String TEST_CLASS_NAME = "com.anbai.sec.classloader.TestHelloWorld"; // TestHelloWorld类字节码 public static byte[] TEST_CLASS_BYTES = new byte[]{ -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11, 0, 0, 0, 2, 0, 12 }; @Override public Class<?> findClass(String name) throws ClassNotFoundException { // 只处理TestHelloWorld类 if (name.equals(TEST_CLASS_NAME)) { // 调用JVM的native方法定义TestHelloWorld类 return defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length); } return super.findClass(name); } /** * 使用自定义类加载器加载TestHelloWorld类字节码并调用hello方法示例,等价于如下代码: * <p> * * </p> * * @param args */ public static void main(String[] args) { // 创建自定义的类加载器 TestClassLoader loader = new TestClassLoader(); try { // 使用自定义的类加载器加载TestHelloWorld类 Class testClass = loader.loadClass(TEST_CLASS_NAME); // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld(); Object testInstance = testClass.newInstance(); // 反射获取hello方法 Method method = testInstance.getClass().getMethod("hello"); // 反射调用hello方法,等价于 String str = t.hello(); String str = (String) method.invoke(testInstance); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } } }
首先继承ClassLoader 重写他的方法,这里定义了TEST_CLASS_NAME就是TestHelloWorld的包名到名字
看这个findClass,做一个判断,名字是否是我们所写的TestHelloWorld,然后return defineClass
是调用JVM的方法去定义TestHellowWorld类;
如果不是我们想创建的对象就
return super.findClass(name);
用super调用父类的findclass去创建。
这里代码的main方法就是这样的流程,注意看注释
创建自定义的类加载器loader对象-->用我们重写的类加载器去加载TestHelloWorld类
-->然后通过反射该类,获取对应对象-->然后反射调用对象来invoke调用到hello方法
-->最后通过hello方法的返回str也就是hello world 输出
/** * 使用自定义类加载器加载TestHelloWorld类字节码并调用hello方法示例,等价于如下代码: * <p> * * </p> * * @param args */ public static void main(String[] args) { // 创建自定义的类加载器 TestClassLoader loader = new TestClassLoader(); try { // 使用自定义的类加载器加载TestHelloWorld类 Class testClass = loader.loadClass(TEST_CLASS_NAME); // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld(); Object testInstance = testClass.newInstance(); // 反射获取hello方法 Method method = testInstance.getClass().getMethod("hello"); // 反射调用hello方法,等价于 String str = t.hello(); String str = (String) method.invoke(testInstance); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } }
ok来打断点走一遍整个调用流程首先是在加载我们的TestHelloWorld类
然后以为名字是if判断对应的名字,所以会进入到defineClass方法里面
defineClass是会返回一个对象的
我们用的是58行断点那里的testClass来接收这个返回值
这个返回值也就是TestHelloWorld的对象了,然后我们通过反射去调用这个对象
然后通过testInstance来反射获取到Hello方法
最后就是通过反射调用hello方法来执行,返回给str 输出了hello world:
刚开始理解起来确实有些难,慢慢来把,学习之路,慢就是快~