本文将从多个方面对Spring Boot本地类和Jar包类加载顺序做详细的阐述,并给出相应的代码示例。
一、类加载机制概述
在介绍Spring Boot本地类和Jar包类加载顺序之前,有必要简要介绍一下Java的类加载机制。
Java虚拟机在程序运行期间动态加载类,并将类的字节码转换成运行时数据结构,形成可执行代码。类的加载过程由ClassLoader类及其子类完成。
Java虚拟机规范将ClassLoader类设计成了一个抽象类,用以描述Java虚拟机中存在的类加载器,用以加载Class文件。ClassLoader类的任务是把class文件字节码转化为java.lang.Class的一个实例。而Class实例通常存放于运行时数据区的方法区内。
二、本地类和Jar包类加载顺序详解
2.1 Jar包优先于本地类
在Java类加载过程中,如果系统中存在多个相同的类文件,则优先加载JVM类路径下的Jar包中的class文件,而忽略本地类路径下的同名class文件。
为了说明这一点,我们可以自己手写一个同名class文件,并同时存放在JVM类路径下和本地类路径下。接着,我们在代码中进行加载并打印该class文件的内容。
// 在JVM类路径下创建SameClass.java
package com.example.demo;
public class SameClass {
public static void printFile() {
System.out.println("This is a file in JVM classpath.");
}
}
// 在本地类路径下创建SameClass.java,内容和上述相同
package com.example.demo;
public class SameClass {
public static void printFile() {
System.out.println("This is a file in local classpath.");
}
}
// 在代码中进行加载并打印SameClass类文件的内容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoadingOrderDemoApplication.class.getClassLoader();
Class clazz = classLoader.loadClass("com.example.demo.SameClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
运行上面的代码之后,我们发现输出结果是”This is a file in JVM classpath.”。
这是因为JVM类路径下的SameClass.class文件优先被加载,而本地类路径下的同名文件被忽略了。
2.2 本地类优先于系统类
对于Jar包中不存在的类文件,Java会首先从本地文件系统中查找,然后才会查找系统类库(如JDK中的rt.jar)。
同样地,我们可以手写一个只在本地类路径下存在的class文件,并在代码中进行加载和打印。
// 在本地类路径下创建OnlyLocalClass.java
package com.example.demo;
public class OnlyLocalClass {
public static void printFile() {
System.out.println("This is a file in local classpath, but not in the system classpath.");
}
}
// 在代码中进行加载并打印OnlyLocalClass类文件的内容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = ClassLoadingOrderDemoApplication.class.getClassLoader();
Class clazz = classLoader.loadClass("com.example.demo.OnlyLocalClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
运行上述代码之后,我们可以发现打印结果为”This is a file in local classpath, but not in the system classpath.”。
由此可见,本地类优先于系统类。
2.3 父ClassLoader优先于子ClassLoader
对于同一份class文件,父ClassLoader的加载顺序优先于子ClassLoader。
我们可以通过自定义ClassLoader来模拟这种情况,并在代码中进行加载和打印。
// 自定义ClassLoader
package com.example.demo;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
@Override
protected Class findClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = this.getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}
// 在本地类路径下创建ParentClass.java
package com.example.demo;
public class ParentClass {
public static void printFile() {
System.out.println("This is a file in parent classloader.");
}
}
// 在代码中进行加载并打印ParentClass类文件的内容
package com.example.demo;
public class ClassLoadingOrderDemoApplication {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader parentClassLoader = new MyClassLoader();
ClassLoader childClassLoader = new MyClassLoader();
parentClassLoader.loadClass("com.example.demo.ParentClass");
Class clazz = childClassLoader.loadClass("com.example.demo.ParentClass");
clazz.getMethod("printFile").invoke(null, null);
}
}
运行上述代码之后,我们可以发现打印结果为”This is a file in parent classloader.”,即父ClassLoader先于子ClassLoader加载了该class文件。
三、小结
本文从类加载机制概述、Jar包与本地类加载顺序、父ClassLoader与子ClassLoader的加载顺序等多个方面,对Spring Boot本地类和Jar包类加载顺序做了详细剖析。
Java虚拟机能够动态加载类,并将类的字节码转换成运行时数据结构形成可执行代码,类的加载过程由ClassLoader类及其子类完成。
原创文章,作者:OISGV,如若转载,请注明出处:https://www.506064.com/n/373731.html
微信扫一扫
支付宝扫一扫