开发过程中经常会遇到java.lang.ClassNotFoundExcetpion的异常,为何会有这个异常呢?因为你想要使用的类并未加载到虚拟机当中。本文将介绍java的类加载器,让你了解一个class文件加载到虚拟机中的机制,并介绍了如何自定义类加载器。结合代码验证理论。
JVM预定义了三种类加载器,分别是:
启动类加载器是使用本地代码实现的,类似于我们看源码时的native方法,找不到具体的class。它负载将JAVA_HOME/lib下的核心类库或-Xbootclasspath选项指定的路径下的jar加载到虚拟机当中。
System.getProperty(“sun.boot.class.path”)参数可以查看该类加载器加载的路径。
因为没有具体的class,所以没法看类图。
扩展类加载器是sun.misc.Launcher$ExtClassLoader。它负责将JAVA_HOME /lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。
System.getProperty(“java.ext.dirs”)参数可以查看该类加载器加载的路径。
类图如下:
它是sun.misc.Launcher的内部类。
系统类加载器是sun.misc.Launcher$AppClassLoader。它负责将用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径下的类库加载到内存中。开发者可以直接使用系统类加载器。
System.getProperty(“java.class.path”)可查看该类加载器加载的路径。
类图如下:
与扩展类加载器一样,它也是sun.misc.Launcher的内部类。
下面的代码分别打印了三种类加载器加载了哪些路径的jar包,验证上面的介绍
package ClassLoader; /** * @author ludengke * @title: ClassLoader.TestClassLoader * @projectName springcloud-demo * @description: TODO * @date 2021/12/2615:15 */ public class TestClassLoader { public static void main(String[] args) { bootStrapClassLoader(); extClassLoader(); appClassLoader(); // testClassLoader(); } /** * 测试TestBean被哪个类加载器加载的,这个加载器加载的路径是啥 */ // private static void testClassLoader(){ // try { // //查看当前系统类路径中包含的路径条目 // System.out.println(System.getProperty("java.class.path")); // //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean // Class typeLoaded = Class.forName("sun.net.spi.nameservice.dns.DNSNameService"); // Class typeLoaded2 = Class.forName("ClassLoader.TestBean"); // //查看被加载的TestBean类型是被那个类加载器加载的 // System.out.println(typeLoaded.getClassLoader()); // System.out.println(typeLoaded2.getClassLoader()); // } catch (Exception e) { // e.printStackTrace(); // } // } /*** *@Description 测试bootstrap加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:17 */ private static void bootStrapClassLoader(){ System.out.println("bootstrap start:"); System.out.println(System.getProperty("sun.boot.class.path")); System.out.println("bootstrap end:"); } /*** *@Description extClassLoader加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:26 */ private static void extClassLoader(){ //testClassLoaderParent(); System.out.println("ext start"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println("ext end"); } /** * 获取类加载器的parent */ private static void testClassLoaderParent(){ System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); } /** *@Description appClassLoader加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:29 */ private static void appClassLoader(){ System.out.println("app start"); System.out.println(System.getProperty("java.class.path")); System.out.println("app end"); } }
测试类执行结果如下:
D:\work\java\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=54689:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\study\target\classes;D:\work\maven\mavenstore\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar ClassLoader.TestClassLoader bootstrap start: D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\java\jre\lib\sunrsasign.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\classes bootstrap end: ext start D:\work\java\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext ext end app start D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\study\target\classes;D:\work\maven\mavenstore\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar app end Process finished with exit code 0
从测试结果可以看出系统类加载器也加载了启动类加载器、扩展类加载应该加载的路径,这会不会有问题呢?
应该不会,下面介绍类加载机制。
类加载器的继承关系如下,这里的继承关系不是真正的继承,是类加载器的父子委托关系
双亲委派机制:
package ClassLoader; public class TestBean { public TestBean() { } }
测试类,主要看testClassLoader()方法
package ClassLoader; /** * @author ludengke * @title: ClassLoader.TestClassLoader * @projectName springcloud-demo * @description: TODO * @date 2021/12/2615:15 */ public class TestClassLoader { public static void main(String[] args) { // bootStrapClassLoader(); // extClassLoader(); // appClassLoader(); testClassLoader(); } /** * 测试TestBean被哪个类加载器加载的,这个加载器加载的路径是啥 */ private static void testClassLoader(){ try { //查看当前系统类路径中包含的路径条目 // System.out.println(System.getProperty("java.class.path")); //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean Class typeLoaded = Class.forName("sun.net.spi.nameservice.dns.DNSNameService"); Class typeLoaded2 = Class.forName("ClassLoader.TestBean"); //查看被加载的TestBean类型是被那个类加载器加载的 System.out.println(typeLoaded.getClassLoader()); System.out.println(typeLoaded2.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } /*** *@Description 测试bootstrap加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:17 */ private static void bootStrapClassLoader(){ System.out.println("bootstrap start:"); System.out.println(System.getProperty("sun.boot.class.path")); System.out.println("bootstrap end:"); } /*** *@Description extClassLoader加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:26 */ private static void extClassLoader(){ // testClassLoaderParent(); System.out.println("ext start"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println("ext end"); } /** * 获取类加载器的parent */ private static void testClassLoaderParent(){ System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); } /** *@Description appClassLoader加载的路径 *@Param [] *@Return void *@Author ludengke *@Date 2021/12/26 *@Time 15:29 */ private static void appClassLoader(){ System.out.println("app start"); System.out.println(System.getProperty("java.class.path")); System.out.println("app end"); } }
sun.misc.Launcher$ExtClassLoader@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2 Process finished with exit code 0
D:\work\java\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=62498:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\study\target\classes;D:\work\maven\mavenstore\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar ClassLoader.TestClassLoader sun.misc.Launcher$ExtClassLoader@7f31245a sun.misc.Launcher$ExtClassLoader@7f31245a Process finished with exit code 0
D:\work\java\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=62680:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\study\target\classes;D:\work\maven\mavenstore\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar ClassLoader.TestClassLoader sun.misc.Launcher$ExtClassLoader@7f31245a sun.misc.Launcher$ExtClassLoader@7f31245a Process finished with exit code 0
为什么需要自定义类加载器呢?
因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader。
而且可以使用加密的class文件,加载该class文件时,只有懂得如何解密才能使用,这时也需要自定义的类加载器。
而且我们可以根据自己的需求,对class文件进行加密和解密。
package ClassLoader; public class TestBean { public TestBean() { } public void testMethod(){ System.out.println("ClassLoader.TestBean.testMethod()"); } }
package ClassLoader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * @author ludengke * @title: MyCloassLoader * @projectName springcloud-demo * @description: TODO * @date 2022/1/522:41 */ public class MyCloassLoader extends ClassLoader { private String path; public MyCloassLoader(String path) { this.path = path; } /** * 重父类的该方法 * * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class log = null; // 获取该class文件字节码数组 byte[] classData = getData(); if (classData != null) { // 将class的字节码数组转换成Class类的实例 log = defineClass(name, classData, 0, classData.length); } return log; } /** * 获取class文件的字节码 * @return */ private byte[] getData() { File file = new File(path); if (file.exists()) { FileInputStream in = null; ByteArrayOutputStream out = null; try { in = new FileInputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int size = 0; while ((size = in.read(buffer)) != -1) { out.write(buffer, 0, size); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } return out.toByteArray(); } else { return null; } } }
package ClassLoader; import java.lang.reflect.Method; /** * @author ludengke * @title: ClassLoader.TestClassLoader * @projectName springcloud-demo * @description: TODO * @date 2021/12/2615:15 */ public class TestClassLoader { public static void main(String[] args) { testMyClassLoader(); } /** * 测试自定义的类加载器 */ private static void testMyClassLoader(){ try { //实例类加载器 MyCloassLoader cloassLoader=new MyCloassLoader("D:\\work\\springcloud-demo\\study\\target\\classes\\ClassLoader\\TestBean.class"); System.out.println("自定义的类加载器的父类加载器是:"+cloassLoader.getParent()); //通过自定义的类加载器加载类 Class testBeanClass=cloassLoader.findClass("ClassLoader.TestBean"); //加载出来的类的类加载器 System.out.println("类加载器是:" + testBeanClass.getClassLoader()); //通过反射调用类的方法 Method method = testBeanClass.getDeclaredMethod("testMethod"); Object object = testBeanClass.newInstance(); //调用类的方法 method.invoke(object); } catch (Exception e) { e.printStackTrace(); } } }
D:\work\java\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=63921:C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath D:\work\java\jre\lib\charsets.jar;D:\work\java\jre\lib\deploy.jar;D:\work\java\jre\lib\ext\access-bridge-64.jar;D:\work\java\jre\lib\ext\cldrdata.jar;D:\work\java\jre\lib\ext\dnsns.jar;D:\work\java\jre\lib\ext\jaccess.jar;D:\work\java\jre\lib\ext\jfxrt.jar;D:\work\java\jre\lib\ext\localedata.jar;D:\work\java\jre\lib\ext\nashorn.jar;D:\work\java\jre\lib\ext\sunec.jar;D:\work\java\jre\lib\ext\sunjce_provider.jar;D:\work\java\jre\lib\ext\sunmscapi.jar;D:\work\java\jre\lib\ext\sunpkcs11.jar;D:\work\java\jre\lib\ext\zipfs.jar;D:\work\java\jre\lib\javaws.jar;D:\work\java\jre\lib\jce.jar;D:\work\java\jre\lib\jfr.jar;D:\work\java\jre\lib\jfxswt.jar;D:\work\java\jre\lib\jsse.jar;D:\work\java\jre\lib\management-agent.jar;D:\work\java\jre\lib\plugin.jar;D:\work\java\jre\lib\resources.jar;D:\work\java\jre\lib\rt.jar;D:\work\springcloud-demo\study\target\classes;D:\work\maven\mavenstore\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar ClassLoader.TestClassLoader 自定义的类加载器的父类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2 类加载器是:ClassLoader.MyCloassLoader@1540e19d ClassLoader.TestBean.testMethod() Process finished with exit code 0
1、自定义的类加载器需要继承java.lang.ClassLoader,重写findClass方法。
2、findClass方法的内容是加载指定路径下的class文件,将其转为字节数组,调用父类的defineClass方法将字节数组转为Class对象。
3、通过测试类可以看出,自定义类加载器的父类加载器是sun.misc.Launcher$AppClassLoader。
4、通过测试结果可以看出,自定义类加载器成功加载指定路径下的class文件,设计成功。
参考博客
https://www.jianshu.com/p/1e4011617650
https://blog.csdn.net/huazai30000/article/details/85296671