1、类加载运行过程
当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM。
下面通过一个代码示例来理解一下loadClass的5个过程:
package com.tuling.class01;
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
上面示例代码的类加载过程,如下图所示:
类被加载到方法区中后主要包含:运行时常量池,类型信息、字段信息、方法信息、类加载器的引用、对应的Class实例的引用等信息
- 类加载器的引用:这个类到类加载器的引用
- 对应Class实例的引用:类加载器在加载类信息放到方法区后,会创建一个对应的Class类型的对象实例放到堆中;作为开发人员访问方法区中类定义的入口和切入点。
下面使用代码示例来证明上面的观点:
package com.tuling.class01;
public class TestDynamicLoad {
static {
System.out.println("load TestDynamicLoad static block");
}
public static void main(String[] args) {
new A();
System.out.println("load test");
B b = null; //B不会被加载,除非这里执行new B();
}
}
class A {
static {
System.out.println("load A static block");
}
public A() {
System.out.println("init A");
}
}
class B {
static {
System.out.println("load B static block");
}
public B() {
System.out.println("init B");
}
}
运行结果如下:
2、类加载器和双亲委派机制
上面的类加载过程主要是通过类加载器来实现的,Java里有如下集中类加载器:
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charset.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的jar包
- 应用程序类加载器:负责加载ClassPath路径下的jar包,主要是加载自己开发的那些类
- 自定义类加载器:负责加载用户自定义路径下的类包
下面通过一个代码示例来分析类加载器:
package com.tuling.class01;
import sun.misc.Launcher;
import java.net.URL;
import java.util.Arrays;
public class TestJdkClassLoader {
public static void main(String[] args) {
//因为引导类加载器是C++语言实现的,所以Java中设置为null
System.out.println("引导类加载器:" + String.class.getClassLoader());
System.out.println("扩展类加载器:" + com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());
System.out.println("应用程序类加载器:" + TestJdkClassLoader.class.getClassLoader());
System.out.println();
//三种类加载器的父子关系
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("默认类加载器:" + appClassLoader);
System.out.println("应用程序类加载器的父亲:" + extClassLoader);
System.out.println("扩展类加载器的父亲:" + bootstrapClassLoader);
System.out.println();
System.out.println("bootstrapClassLoader加载以下文件:");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
System.out.println(Arrays.toString(urLs));
System.out.println();
System.out.println("extClassLoader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行上面的代码,输出如下,可以看出appClassLoader除了有:\CodeWorkspace\PersonCode\demo\target\classes;
路径,还有一些bootstrapClassLoader和extClassLoader的路径。由于双亲委派机制的问题,appClassLoader只加载CodeWorkspace\PersonCode\demo\target\classes
这个路径下的自己开发的代码。
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar=13132:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar" com.tuling.class01.TestJdkClassLoader
引导类加载器:null
扩展类加载器:sun.misc.Launcher$ExtClassLoader@a09ee92
应用程序类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
默认类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
应用程序类加载器的父亲:sun.misc.Launcher$ExtClassLoader@a09ee92
扩展类加载器的父亲:null
bootstrapClassLoader加载以下文件:
[file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/resources.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/rt.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/sunrsasign.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/jsse.jar,
ile:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/jce.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/charsets.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/lib/jfr.jar,
file:/C:/Program%20Files/Java/jdk1.8.0_212/jre/classes]
extClassLoader加载以下文件:
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext
appClassLoader加载以下文件:
C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;
D:\CodeWorkspace\PersonCode\demo\target\classes;
C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;
C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;
C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;
C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;
C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;
C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;
C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;
C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;
C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;
C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;
C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;
C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;
C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;
C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;
C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;
C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;
C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;
C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;
C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;
C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;
C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar;
C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar
Process finished with exit code 0
3、类加载器初始化过程
下面分析一下Launcher类的初始化源码:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化extClassLoader,并将父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//初始化appClassLoader,并将extClassLoader设置为父加载器
//将appClassLoader设置为Launcher默认类加载器,我们一般都用它加载我们写的应用程序
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置当前线程的上下文类加载器为appClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
//省略下面部分代码
}
4、双亲委派机制
上图就是JVM默认类加载机制,即双亲委派机制。
假设我们要加载自己开发的Demo类,下面通过Demo类加载过程来解释一下上面的流程:
下面是ClassLoader类的loadClass方法源码,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先,检查当前类加载器是否已经加载了该类
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();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
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;
}
}
为什么要设计双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改。
- 避免类的重复加载:当父类已经加载了该类时,子加载器没必要再加载一遍,保证被加载类的唯一性。
下面是一个尝试篡改核心类的Demo示例
因为java.lang.String属于java核心类库,所以会通过引导类加载器加载rt.jar中的String类,rt.jar中的String类没有main方法,所以会报错。
package java.lang;
/**
* @author zhao.hualuo
* Create at 2022/3/25
*/
public class String {
public static void main(String[] args) {
System.out.println("my custom String Class");
}
}
执行结果如下:
"D:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\lib\idea_rt.jar=65038:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\CodeWorkspace\Demo\out\production\Demo" java.lang.String
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
Process finished with exit code 1
5、全盘负责委托机制
“全盘负责”是指当一个ClassLoader装载一个类时,该类所依赖及引用的类也由这个ClassLoader引入(除非显式的使用另外一个ClassLoader)
如:加载Demo类时,通过双亲委派机制决定通过AppClassLoader进行装载。若Demo类内引用了User类,这个User类也默认使用AppClassLoader进行装载
6、自定义类加载器
自定义类加载器需要继承java.lang.ClassLoader类,该类有两个核心方法:
一个是loadClass(String, boolean),实现了双亲委派机制
一个是findClass(String),默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
下面进行代码示例演示:
准备工作,首先准备一个Demo类,class对象如下图,将class对象放到D:/data目录下
package com.tuling.class01;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoadTest {
public static void main(String[] args) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,
//其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/data");
//D盘创建D:\data\com\tuling\class01 几级目录,将Demo.class丢入该目录
Class<?> clazz = classLoader.loadClass("com.tuling.class01.Demo");
Object instance = clazz.newInstance();
Method method = clazz.getDeclaredMethod("display", null);
method.invoke(instance, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = loadByte(name);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
}
}
运行结果如下,可见此时应用的类加载器就是我们的MyClassLoader:
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:5515,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.1\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar" com.tuling.class01.MyClassLoadTest
Connected to the target VM, address: '127.0.0.1:5515', transport: 'socket'
local path demo
com.tuling.class01.MyClassLoadTest$MyClassLoader
Disconnected from the target VM, address: '127.0.0.1:5515', transport: 'socket'
Process finished with exit code 0
需要注意的是,这时候只是实现了自定义类加载器,但是并没有打破双亲委派机制,若AppClassLoader也能加载到Demo类时,就会使用AppClassLoader进行加载,不会使用我们的自定义类加载器。
下面通过一个实例来验证上述观点,将Demo放到工程目录下,同时改造display方法的输出语句
再次执行代码自定义类加载器中的main方法,运行结果如下,执行的类加载器为AppclassLoader,输出语句也有了变化:
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar=3906:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar" com.tuling.class01.MyClassLoadTest
project demo
sun.misc.Launcher$AppClassLoader
Process finished with exit code 0
7、打破双亲委派机制
上面第六步分析写到:
为了能加载自己想要加载的类,我们实现了findClass方法。
那么为了打破双亲委派机制,是不是要重写loadClass方法?下面继续使用上面的代码演示:
package tuling.class01;
import java.io.FileInputStream;
import java.lang.reflect.Method;
/**
* @author zhao.hualuo
* Create at 2022/3/25
*/
public class MyClassLoaderTest {
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("tuling.class01.Demo");
Object instance = clazz.newInstance();
Method method = clazz.getDeclaredMethod("display", null);
method.invoke(instance, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
//重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//First, check if the class has already been loaded
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
//if stall not found, then invoke findClass in order
//to find the class
long nanoTime = System.nanoTime();
if (name.startsWith("tuling.class01")) {
//如果是我们自己开发的业务类,使用我们自己的类加载器加载,不走双亲委派机制
clazz = findClass(name);
} else {
//如果不是我们开发的,需要走双亲委派机制。
//如:Object类是所有类的超类,
//若不使用双亲委派机制,所有的java基础包中的类都需要我们手动开发
clazz = this.getParent().loadClass(name);
}
//this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(nanoTime);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
//重写findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//加载文件
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
}
}
在上面的代码中,我们重写了loadClass方法,覆盖了原有的双亲加载机制,若我们指定的目录下无Demo.class,即使MyClassLoaderTest路径下有Demo.java,也不会通过AppClassLoader加载。
"D:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\lib\idea_rt.jar=56937:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\CodeWorkspace\Demo\target\production\Demo" tuling.class01.MyClassLoaderTest
java.io.FileNotFoundException: D:\test\tuling\class01\Demo.class (系统找不到指定的路径。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at tuling.class01.MyClassLoaderTest$MyClassLoader.loadByte(MyClassLoaderTest.java:70)
at tuling.class01.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:58)
at tuling.class01.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:40)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at tuling.class01.MyClassLoaderTest.main(MyClassLoaderTest.java:14)
Exception in thread "main" java.lang.ClassNotFoundException
at tuling.class01.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:63)
at tuling.class01.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:40)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at tuling.class01.MyClassLoaderTest.main(MyClassLoaderTest.java:14)
Process finished with exit code 1
如果指定目录下有该class文件,则可以使用我们的自定义类加载器成功加载:
"D:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\lib\idea_rt.jar=57440:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\CodeWorkspace\Demo\target\production\Demo" tuling.class01.MyClassLoaderTest
我是指定目录下的Demo.class
tuling.class01.MyClassLoaderTest$MyClassLoader
Process finished with exit code 0
上面的示例告一段落
下面开始尝试另一种思路,我们能不能使用上面打破双亲委派机制的办法重写java基础包中的类,如String类。
创建一个java.lang.String类,如下:
package java.lang;
/**
* @author zhao.hualuo
* Create at 2022/3/25
*/
public class String {
public void sout() {
System.out.println("my custom String Class");
}
}
放到指定目录下(需将程序中自建的String类删掉):
和上面的代码相比,只修改了main方法,下面贴出来main方法:
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("java.lang.String");
Object instance = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(instance, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
运行结果如下,禁止使用的包名:java.lang
"D:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\lib\idea_rt.jar=58239:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\CodeWorkspace\Demo\target\production\Demo" tuling.class01.MyClassLoaderTest
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:655)
at java.lang.ClassLoader.defineClass(ClassLoader.java:754)
at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
at tuling.class01.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:61)
at tuling.class01.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:40)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at tuling.class01.MyClassLoaderTest.main(MyClassLoaderTest.java:14)
Exception in thread "main" java.lang.ClassNotFoundException
at tuling.class01.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:64)
at tuling.class01.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:40)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at tuling.class01.MyClassLoaderTest.main(MyClassLoaderTest.java:14)
Process finished with exit code 1
通过追踪代码可以看出,自定义类加载器读取我们本地的String类文件是吗,没有任何问题的,但是在执行defineClass方法时进行了校验:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
defineClass方法中有一个校验方法preDefineClass,可以看到我们上面的报错信息就是这个方法吐出来的:
/* Determine protection domain, and check that:
- not define java.* class,
- signer of this class matches signers for the rest of the classes in
package.
*/
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
8、Tomcat打破双亲委派机制
首先提出问题:Tomcat使用默认类加载机制行不行?
再看看我们上面提出的问题:Tomcat使用默认类加载机制行不行?
Tomcat违背了双亲委派模型
每个WebappClassLoader加载自己的目录下的class文件,不会传递给父加载器,打破了双亲委派机制。
下面模拟实现Tomcat的WebappClassLoader,加载不同版本的类,实现相互隔离:
1、首先准备一个Demo.class
放到D:\data\com\tuling\class01
目录下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.tuling.class01;
public class Demo {
public Demo() {
}
public void display() {
System.out.println("我是 D:/data 目录下的Demo.class");
}
}
2、再准备一个Demo.class
放到D:\data2\com\tuling\class01
目录下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.tuling.class01;
public class Demo {
public Demo() {
}
public void display() {
System.out.println("我是 D:/data2 目录下的Demo.class");
}
}
3、下面是自定义类加载器代码,我们的MyClassLoader就相当于Tomcat的WebappClassLoader:
package com.tuling.class01;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoadTest {
public static void main(String[] args) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/data");
Class<?> clazz = classLoader.loadClass("com.tuling.class01.Demo");
Object instance = clazz.newInstance();
Method method = clazz.getDeclaredMethod("display", null);
method.invoke(instance, null);
System.out.println(clazz.getClassLoader().getClass().getName());
MyClassLoader classLoader2 = new MyClassLoader("D:/data2");
Class<?> clazz2 = classLoader2.loadClass("com.tuling.class01.Demo");
Object instance2 = clazz2.newInstance();
Method method2 = clazz2.getDeclaredMethod("display", null);
method2.invoke(instance2, null);
System.out.println(clazz2.getClassLoader().getClass().getName());
}
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
//重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//First, check if the class has already been loaded
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
//if stall not found, then invoke findClass in order
//to find the class
long nanoTime = System.nanoTime();
if (name.startsWith("com.tuling.class01")) {
//如果是我们自己开发的业务类,使用我们自己的类加载器加载,不走双亲委派机制
clazz = findClass(name);
} else {
//如果不是我们开发的,需要走双亲委派机制。
//如:Object类是所有类的超类,
//若不使用双亲委派机制,所有的java基础包中的类都需要我们手动开发
clazz = this.getParent().loadClass(name);
}
//this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(nanoTime);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
//重写findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//加载文件
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.","/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
}
}
4、执行结果:
"C:\Program Files\Java\jdk1.8.0_212\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\lib\idea_rt.jar=5388:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_212\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_212\jre\lib\rt.jar;D:\CodeWorkspace\PersonCode\demo\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-http\4.1.0.5\gexin-rp-sdk-http-4.1.0.5.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-template\4.0.0.24\gexin-rp-sdk-template-4.0.0.24.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-sdk-base\4.0.0.30\gexin-rp-sdk-base-4.0.0.30.jar;C:\Users\Administrator\.m2\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;C:\Users\Administrator\.m2\repository\com\gexin\platform\gexin-rp-fastjson\1.0.0.3\gexin-rp-fastjson-1.0.0.3.jar;C:\Users\Administrator\.m2\repository\com\getui\push\restful-sdk\1.0.0.1\restful-sdk-1.0.0.1.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.14\httpcore-4.4.14.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar;C:\Users\Administrator\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar" com.tuling.class01.MyClassLoadTest
我是 D:/data 目录下的Demo.class
com.tuling.class01.MyClassLoadTest$MyClassLoader
我是 D:/data2 目录下的Demo.class
com.tuling.class01.MyClassLoadTest$MyClassLoader
Process finished with exit code 0