首页 > 编程知识 正文

Spring Boot本地类和Jar包类加载顺序深度剖析

时间:2023-11-19 10:18:44 阅读:290913 作者:CXOG

本文将从多个方面对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类及其子类完成。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。