百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

【JVM类加载】线程上下文加载器分析ServiceLoader.load源码详解

toyiye 2024-08-22 23:07 3 浏览 0 评论

线程上下文的类加载器(setContextClassLoader)

  • 当前类加载器(Current ClassLoader)
    每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是是所依赖的类),如果classX引用ClassY,那么ClassX的类加载器就会去加载ClassY(前提是ClassY尚未加载)
  • 线程上下文类加载器(Context ClassLoader)
  • 线程上下文类加载器是从JDK1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器

    线程上下文类加载器的重要性

    • SPI :(Service Provider Interface)服务提供者接口如:jdbc 是用来声明接口制定一些标准(仅仅通过双亲委托来加载类似 父加载器无法看到子类加载器(命名空间) 但是父加载器加载 如 根加载器加载一些rt.jar包里的一些内容如jdbc定义的接口,需要调外部别人实现的一些类时就无法访问到)
    • 父类ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的classLoader加载对应的类放到内存中,供父类加载器加载的类使用。这就:改变了父classLoader不能使用子ClassLoader或是其他没有直接父子关系的classLoader加载的类的情况。即改变了双亲委托模型。
    • 线程上下文类加载器就是当前线程的Current Classloader
    • 在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层的加载。但是对于SPI来说,有些接口是java核心库所提供的,而java核心库是由启动类加载器来加载的,而这些接口的实现去来自于不同的jar包(厂商提供)java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。
    • 通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载
    public class MyTest24 {
    
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getContextClassLoader()); //AppClassLoader
            System.out.println(Thread.class.getClassLoader()); //null  Thred位于lang包
        }
    }

    如果没有设置线程上下文类加载器,该线程上下文类加载器默认设置为系统类加载器

    
    public class MyTest25 implements Runnable {
    
        private Thread thread;
    
        public MyTest25() {
            this.thread = new Thread(this);
            thread.start();
        }
    
        @Override
        public void run() {
            ClassLoader classLoader = this.thread.getContextClassLoader();
    
            this.thread.setContextClassLoader(classLoader);
    
            System.out.println("Class: " + classLoader.getClass());
            System.out.println("parent: " + classLoader.getParent().getClass());
        }
    
        public static void main(String[] args) {
            new MyTest25();
        }
    }
    
    /*
        Class: class sun.misc.Launcher$AppClassLoader //如果没有设置线程上下文类加载器 默认设置为 系统类加载器 具体在getLauncher 讲解里
        parent: class sun.misc.Launcher$ExtClassLoader //系统类加载器的父类  只是打破加载规则 并不打破包含规则
    
     */

    线程上下文类加载器的一般使用模式


    线程上下文类加载器的一般使用模式(获取->使用->还原)

    
      //获取
            ClassLoader classLoader = Thread.currentThread.getContextClassLoader();
        try{
            //设置要使用的类加载器
            Thread.currentThread.setContextClassLoader(cls);
            //使用
            MyMethod();
        }finally{
            //如果不还原使用的就是设置的类加载器
            //还原
            Thread.currentThread.setContextClassLoader(classLoader);
        }
    

    上例中:
    MyMethod里面则调用了Thread.currentThread().getContextClassLoader(),获取当前线程的上下文类加载器做某些事情 MyMethod()。

    • 如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过的话) 如:启动类加载器扫描不到系统类加载器的内容 上下文类加载器就是为了解决这个问题
    • ContextClassLoader的作用就是为了破坏Java的类加载委托机制(如上)
    • 当高层提供了统一的接口让低层去实现,同时又要在高层加载(或实例化)底层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类(上下文类加载器就是为了解决父类加载器加载的类无法看到子类加载器加载的类)我们可以直接使用getClassLoader获取系统类加载器去加载对应的类为什么还要使用上下文类加载器?

    1.方便,执行的任何代码都在线程中 我们可以随时取出来对应的上下文类加载器使用

    2.解决 高层加载底层类问题

    3.有特定的情况,当前的线程类加载器 不一定是系统类加载器 此时不能加载classpath下的.class文件

    import java.sql.Driver;
    import java.util.Iterator;
    import java.util.ServiceLoader;
    
    public class MyTest26 {
    
        public static void main(String[] args) {
            //Driver驱动连接规范的一些信息
            //load()等价与load(Class<S> service, ClassLoader loader)
            //load(需要加载服务的class,Thread.currentThread().getContextClassLoader())
            ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
            Iterator<Driver> iterator = loader.iterator();
    
            while (iterator.hasNext()){
                Driver driver = iterator.next();
                System.out.println("driver: "+driver.getClass()+",loader: "+driver.getClass().getClassLoader());
            }
    
            System.out.println("当前线程上下文类加载器: "+Thread.currentThread().getContextClassLoader());
            System.out.println("ServiceLoader的类加载器:"+loader.getClass().getClassLoader());
        }
    }
    
    /*
    为什么仅仅是Driver.class 这个接口就能找到以下两个Driver实现类
    服务提供者将提供者(供应商)的程序配置文件放在资源目录META-INF/services目录**下当中。
    该文件的名称(如mysql 加载java.sql.Driver文件 类加载器就可以加载这个文件里面每一行的二进制名(类名的路径 此时对应的类就会被加载))
    driver: class com.alibaba.druid.proxy.DruidDriver,loader: sun.misc.Launcher$AppClassLoader@18b4aac2  所有maven下载的jar都位于classpath下
    driver: class com.alibaba.druid.mock.MockDriver,loader: sun.misc.Launcher$AppClassLoader@18b4aac2
    driver: class com.mysql.cj.jdbc.Driver,loader: sun.misc.Launcher$AppClassLoader@18b4aac2
    ---
    当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2 未设置默认为系统类加载器
    ServiceLoader的类加载器:null --ServiceLoader位于rt.jar包下
     */
    

    ServiceLoader(服务加载器) 源码分析 (jdk1.6之后)


    1.doc官方文档ServiceLoader(服务加载器)在SPI中的重要作用分析

    ServiceLoader就是用于加载提供者在特定位置META-INF/services 的服务对应服务文件名里的二进制文件名的类

    ServiceLoader是一个服务提供加载者

    • 已知服务的接口(抽象类的集合), 用来加载服务特定的实现(如加载JDBC接口实现类)。提供者(厂商)中的类通常实现服务者接口并继承服务本身中定义的类。服务提供者(如JDBC驱动)可以安装在Java平台的实现中以扩展名的形式,比如说以jar文件被放置到任何常用的扩展目录中。提供程序也可以通过将它们添加到应用程序的类路径(classpath)或其他特定于平台的方法 ,让其变得可用。
    • 对于加载目的,服务由单一类型表示,即单个接口或抽象类。(抽象类可以是已使用,但不建议使用。)给定服务的提供者包含一个或多个具体类,用数据扩展这个服务类型和特定于提供程序的代码。提供程序类是典型的不是整个提供者本身,而是一个包含足够内容的代理决定供应商是否能够满足特定要求的信息请求和代码一起,可以根据需要创建实际的提供者。提供者类的细节往往是高度特定于服务的;没有单个类或接口可以统一它们,所以没有这样的类型在这里定义。这个设施实施的唯一要求是提供者实现类必须有一个无参数的构造函数,这样它们才能在加载期间实例化。
    • 服务提供者(供应商)将提供的的程序配置文件放在驱动包资源目录META-INF/services目录下当中。该文件的名称(如mysql 加载java.sql.Driver文件 类加载器就可以加载这个文件里面每一行的二进制名(类名类名的路径 此时对应的类就会被加载))这是一个服务提供者(sun)制定的标准 是完全限定的 href = " . . / lang /服务类型二进制名。
    • 该文件包含一个完全限定的二进制名的具体提供程序列表,每行放置一个(类的二进制名)。空格和每个字符周围的制表符name和空行将被忽略。注释字符“#”;在每行第一个注释字符后面的所有字符将被忽略。文件名必须用UTF-8编码。(这句话的意思 服务提供者必须要一种方式告诉JDK我的提供者(厂商)具体的类是如何定义在 META-INF/services目录下的,而且文件名是服务类型的名字(给父类使用类二进制名)在这个文件中就可以一行指定一个提供者的类名(实现类) 需要上下文加载器加载的类 #是作为注释)


    • 如果在多个配置文件中出现了同一个具体提供者类的名字的话,或在相同的配置文件中名字出现了一次以上,重复项将被忽略。配置文件特定的提供者不需要在相同的jar文件或其他发行版中单独作为提供者本身。提供者必须是可从相同的类加载器,与最初被定位配置文件相同的类加载器加载出来;注意,这个并不是必要的。
    • 提供程序是按需定位和实例化的。一个服务加载器维护一个已加载的提供者缓存。每次调用{@link #iterator}方法都会返回一个迭代器,它首先返回缓存中的所有元素实例化顺序,然后延迟定位和实例化任何剩余的提供程序,依次将每个提供程序添加到缓存中。可以清除缓存通过{@link #reload reload}方法。(提供者当中的类都是延迟实例化和加载的 换句话说就是什么时候使用就什么时候加载,在serviceLoader中存在缓存,缓存已经加载过的服务提供者提供的类)缓存存储位置

    他是按照实例化顺序添加缓存的

    • 服务加载程序总是在调用者的安全上下文中执行。受信任的系统代码通常应该调用这个类中的方法,并且他们返回的迭代器的方法,从一个特权安全上下文。
    • 并不是线程安全的一个类
    • 除非另有说明,否则将null参数传递给any方法将导致抛出{@link NullPointerException}。
    • 假设我们有一个服务类型com.example.CodecSet (提供者类型与sci规范对应) 这是用于表示某些协议的编码器/解码器对的集合。在这种情况下,它是一个抽象类,有两个抽象方法:

    public abstract Encoder getEncoder(String encodingName);//获取解码器

    public abstract Decoder getDecoder(String encodingName);//获取编码器

    • 每个方法返回一个适当的对象或null(如果提供程序)不支持给定的编码。典型的提供者支持不止一个编码。
    • 如果com.example.impl是com.example.CodecSet(是java定义的一个规范接口的)的具体实现类的话,那么它的jar文件也包含一个名为的 META-INF/services/com.example.CodecSet的文件(对应提供者也必须在指定位置有一个同样的接口名的文件)
    • 这个文件会包含如下这一行com.example.impl.StandardCodecs (类似mysql的驱动
    • CodecSet类创建并保存一个服务实例 如上如果是两个就是加载保存两个实例
    
    private static ServiceLoader<CodecSet>codecSetLoader= ServiceLoader.load(CodecSet.class);
    

    此时load(CodecSet.class); 加载的就是/com.example.CodecSet的文件中对应的二进制名的类的实例
    个人理解就是你要对接java规范中那个服务CodecSet就是对应的服务实例

    • The type of the service to be loaded by this loader (加载的泛型就是要加载的服务的类型

    ServiceLoader.load(String service)源码 解析

    
    public static <S> ServiceLoader<S> load(Class<S> service) {
    		//ServiceLoader在核心库中使用根加载器无法加载对应实现类 
    		//所以的获取当前上下文类加载器(未设置 在launcher中设置为系统类加载器 
    		//基本类加载都会加载Launcher类)
    		//核心代码
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            //重载
            return ServiceLoader.load(service, cl);
        }
    
      public static <S> ServiceLoader<S> load(Class<S> service,
                                                ClassLoader loader)
        {
        // 创界一个服务加载器 调用其构造方法 传入需要扫描的规范接口 传入需要加载的类加载器
            return new ServiceLoader<>(service, loader);
        }
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            //如果没有使用系统类加载器有使用自己的
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            //安全问题的判断
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            //调用
            reload();
        }
       public void reload() {
       //清除已加载的提供者缓存
            providers.clear();
            //延迟实例化和加载 (什么时候用什么时候加载) 调用内部类其构造方法
            lookupIterator = new LazyIterator(service, loader);
        }
    
    
     private class LazyIterator
            implements Iterator<S>
        {
    		//文件名
            Class<S> service;
            //加载器
            ClassLoader loader;
            Enumeration<URL> configs = null;
            Iterator<String> pending = null;
            String nextName = null;
    
            private LazyIterator(Class<S> service, ClassLoader loader) {
                this.service = service;
                this.loader = loader;
            }
    
            private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
                      //拼接对应的加载路径  PREFIX 在serviceLoader中定义了"META-INF/services/"; 
                      //SPI规定了 所以加载的就是fullName = 其目录下 +服务接口文件名
                        String fullName = PREFIX + service.getName();
                        if (loader == null)
                        	//此时加载指定文件里的类
                            configs = ClassLoader.getSystemResources(fullName);
                        else
                       		 //没有加载器系统路径
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                //迭代扫描目录
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
                    //解析文件
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    
            private S nextService() {
                if (!hasNextService())
                    throw new NoSuchElementException();
                 //文件中对应的每一行二进制类名
                String cn = nextName;
                nextName = null;
                Class<?> c = null;
                try {
                    //使用loader加载对应类名并不实例化 
                    c = Class.forName(cn, false, loader);
                } catch (ClassNotFoundException x) {
                    fail(service,
                         "Provider " + cn + " not found");
                }
                if (!service.isAssignableFrom(c)) {
                    fail(service,
                         "Provider " + cn  + " not a subtype");
                }
                try {
                	//实例化
                    S p = service.cast(c.newInstance());
                    providers.put(cn, p);
                    return p;
                } catch (Throwable x) {
                    fail(service,
                         "Provider " + cn + " could not be instantiated",
                         x);
                }
                throw new Error();          // This cannot happen
            }
    		//下一行 hasNext next remove 用于迭代
            public boolean hasNext() {
                if (acc == null) {
                    return hasNextService();
                } else {
                    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                        public Boolean run() { return hasNextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
    		//当前行数据
            public S next() {
                if (acc == null) {
                    return nextService();
                } else {
                    PrivilegedAction<S> action = new PrivilegedAction<S>() {
                        public S run() { return nextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
    
            public void remove() {
                throw new UnsupportedOperationException();
            }
    
        }
    
    

    当设置上下文为扩展类加载器,此时驱动在当前目录下 此时就无法找到对应的jar 此时驱动里的实现类就无法加载

    
    public class MyTest26 {
    
        public static void main(String[] args) {
            //设置上下文为扩展类加载器
            Thread.currentThread().setContextClassLoader(MyTest23.class.getClassLoader().getParent());
    
            //Driver驱动连接规范的一些信息
            //load()等价与load(Class<S> service, ClassLoader loader)
            //load(需要加载服务的class,Thread.currentThread().getContextClassLoader())
            ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
            Iterator<Driver> iterator = loader.iterator();
    
            while (iterator.hasNext()) {
                Driver driver = iterator.next();
                System.out.println("driver: " + driver.getClass() + ",loader: " + driver.getClass().getClassLoader());
            }
    
            System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
            System.out.println("ServiceLoader的类加载器:" + loader.getClass().getClassLoader());
        }
    }

    打印

    当前线程上下文类加载器: sun.misc.Launcher$ExtClassLoader@1eb44e46
    ServiceLoader的类加载器:null

    问题分析P31

    
    public class MyTest27 {
    
        public static void main(String[] args) throws Exception {
            Class.forName("com.mysql.jdbc.Driver");
            DriverManager.getConnection("jdbc:mysql://localhost:3306/mytestdb","username","password");
        }
    }

    注意的点: 此时判断是否是同一个类加载器加载的(命名空间) 避免找不到对应的类

    相关推荐

    # Python 3 # Python 3字典Dictionary(1)

    Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

    Python第八课:数据类型中的字典及其函数与方法

    Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

    Python中字典详解(python 中字典)

    字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

    Python3.9又更新了:dict内置新功能,正式版十月见面

    机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

    Python3 基本数据类型详解(python三种基本数据类型)

    文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

    一文掌握Python的字典(python字典用法大全)

    字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

    超级完整|Python字典详解(python字典的方法或操作)

    一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

    Python3.9版本新特性:字典合并操作的详细解读

    处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

    python 自学,字典3(一些例子)(python字典有哪些基本操作)

    例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

    Python3.9中的字典合并和更新,几乎影响了所有Python程序员

    全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

    Python3大字典:《Python3自学速查手册.pdf》限时下载中

    最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

    python学习——字典(python字典基本操作)

    字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

    324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

    如何入门学习python...

    Python3.9中的字典合并和更新,了解一下

    全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

    python3基础之字典(python中字典的基本操作)

    字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

    取消回复欢迎 发表评论:

    请填写验证码