• 类加载(三)


    一、类加载器

    • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库(rt.jar)
    • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库,Java 虚拟机的实现会提供一个扩展库目录(%JAVA_HOME%lib/ext/*.jar)
    • 应用类加载器(application class loader):它根据类路径来加载某个类 (CLASSPATH)

    • 自定义加载器:加载自定义路径的类

     1.AppClassLoader

    二、示例

    1. 类加载

     public class ClassLoaderTree { 
    
        public static void main(String[] args) { 
            ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
            while (loader != null) { 
                System.out.println(loader.toString()); 
                loader = loader.getParent(); 
            } 
        } 
     }

    结果 

     sun.misc.Launcher$AppClassLoader@9304b1   //子
     sun.misc.Launcher$ExtClassLoader@190d11   //父

    2. 

        private static ClassLoader getLocaleClassLoader(File libDir) throws Exception {
            List<URL> urls = new ArrayList<>();
    
            // 获取所有的jar文件  
            File[] jarFiles = libDir.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.endsWith(".jar");
                }
            });
    
            // 将jar文件路径写入集合  
            for (File jarFile : jarFiles) {
                urls.add(jarFile.toURI().toURL());
            }
    
            // 实例化类加载器  
            return new URLClassLoader(urls.toArray(new URL[urls.size()]));
        }
    
        public static void main(String[] args) throws Exception{
            String name = "com.google.common.collect.ImmutableList";
            ClassLoader classLoader = getLocaleClassLoader(new File("/Users/gl/IntelliJProjects/JBase/kafka/build/dependency/"));
            Class clazz = classLoader.loadClass(name);
            System.out.println(clazz);
    
    
            //ClassLoader 为 null, 原因是:
            //Cat.class.getClass()是java.lang.Class对象, 它的加载器是引导类加载器
            Cat.class.getClass().getClassLoader();
    
            //下面是ClassNotFoundException
            clazz = Cat.class.getClassLoader().loadClass(name);
            System.out.println(clazz);
    
        }

     

    三、类加载器的代理模式

      类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。

      代理模式是为了保证 Java 核心库的类型安全所有 Java 应用都至少需要引用java.lang.Object类,也就是说在运行的时候,java.lang.Object

    这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而

    且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的

    Java 核心库的类,是互相兼容的。

      不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同

    类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多框架中都被用到,后面会详细

    介绍。

    四、加载类的过程

      在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启

    动这个加载过程的类加载器,有可能不是同一个。

    真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),

    后者称为初始加载器(initiating loader)。

    在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。

    两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。

    如类 Outer引用了类Inner,则由类 Outer的定义加载器负责启动类Inner的加载过程。

      方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;

      方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。

    类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会

    尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。

     

    五、线程上下文类加载器

      java.lang.Thread中的set/get ContextClassLoader, 如果没有设置ContextClassLoader 的话,线程将继承其父线程的类加载器。初始

    线程的类加载器是系统类加载器。

      前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider

    Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,

    如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。

      SPI 接口中的代码经常需要加载具体的实现类,如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个

    新的 DocumentBuilderFactory的实例。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl而问题在于:

    1. SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的

    2. SPI 的实现是由应用类加载器来加载的

      

    六、类加载器与 Web 容器

      首先尝试去加载某个类,如果找不到再代理给父类加载器。其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例

    外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

  • 相关阅读:
    (转)基于REST架构的Web Service设计
    WPF 简易的喷泉效果
    C# 取Visio模型信息的简易方法
    WPF TextBox按字节长度限制输入
    NPOI导出WPF DataGrid控件显示数据
    WPF--TextBlock的ToolTip附加属性
    【转】WPF 从FlowDocument中找到Hyperlink
    WPF 初学VisifireChart
    WPF 简易进度条效果
    WPF 简易的跑马灯效果
  • 原文地址:https://www.cnblogs.com/yuyutianxia/p/3487715.html
Copyright © 2020-2023  润新知