类加载机制及SPI


最近重温Java类加载及双亲委派机制,并写了一个SPI的例子

 

 

 

 

从网上找了一张图片,对着图片及课堂笔记来梳理下。

首先java自带的类加载器分为BootStrapClassLoader(引导\启动类加载器),ExtClassLoader(扩展类加载器),AppClassLoader(应用程序类加载器)三种,此外还支持用户自己定义的自定义类加载器,加载的是用户自己指定的目录。

BootStrapClassLoader:jvm中,c++处理类加载的这套逻辑,被称为启动类加载器,是由c++编写的,在java中为null,加载的路径是Jre/lib/rt.jar, 在这个过程中会通过启动类加载器,来加载sun.launcher.LauncherHelper,并执行checkAndLoadMain,以及加载main函数所在的类,并启动扩展类加载器、应用类加载器

 

ExtClassLoader: 扩展类加载器,加载的是Jre/lib/ext/*.jar,查看方式:

    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

        URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

        URL[] urls = urlClassLoader.getURLs();
        for (URL url : urls) {
            System.out.println(url);
        }
    }

 

AppClassLoader: 应用类加载器,加载用户程序的类加载器,加载的是CLASS_PATH中指定的所有jar

    public static void main(String[] args) {
        String[] urls = System.getProperty("java.class.path").split(":");

        for (String url : urls) {
            System.out.println(url);
        }

        System.out.println("---------------------------------------------------------");

        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

        URL[] urls1 = classLoader.getURLs();
        for (URL url : urls1) {
            System.out.println(url);
        }
    }

 

 

双亲委派机制:类加载时,AppClassLoader 会先查看自身是否已经加载过当前class文件,如果加载过则直接返回,如果没有加载过,则委托他的父类(ExtClassLoader)尝试进行加载,ExtClassLoader也会先查看自己是否加载过,加载过则直接返回,没有加载过,则继续委派给BootStrapClassLoader,如果直至BootStrapClassLoader都没有加载过,则会AppClassLoader会尝试进行加载。

 

打破双亲委派的方式:改变这个加载流程,不向上委派

package com.learn;

import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {

                    if (name.startsWith("com.learn")) { // 打破双亲委派
                        c = findClass(name);
                    } else {
                        c = this.getParent().loadClass(name);
                    }

                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        return null;
    }
}

 

 

SPI:一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制

 

 

 

 

代码如下:

首先定义一个公共接口

package com.learn.spi.common;

public interface IJdbc {

    void connection();
}

 

然后是两个实现类

package com.learn.spi.mysql;

import com.learn.spi.common.IJdbc;

public class MysqlJdbc implements IJdbc {
    @Override
    public void connection() {
        System.out.println("this is MysqlJdbc...");
    }
}
package com.learn.spi.oracle;

import com.learn.spi.common.IJdbc;

public class OracleJdbc implements IJdbc {
    @Override
    public void connection() {
        System.out.println("this is OracleJdbc...");
    }
}

 

最后main函数使用ServiceLoader调用

package com.learn.spi.gateway;

import com.learn.spi.common.IJdbc;

import java.util.ServiceLoader;

public class GateWayMain {

    public static void main(String[] args) {

        ServiceLoader<IJdbc> iPays = ServiceLoader.load(IJdbc.class);
        for (IJdbc iJdbc : iPays) {
            iJdbc.connection();
        }
        System.out.println("end...");
    }

}

 

META-INF/services文件夹下的文件名定义为接口全路径名,文件内容为实现类全路径名,这里是JDK源码中规定死的

com.learn.spi.oracle.OracleJdbc
com.learn.spi.mysql.MysqlJdbc

 

 

 

 

执行结果:

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM