最近重温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 voidmain(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 voidmain(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会尝试进行加载。
打破双亲委派的方式:改变这个加载流程,不向上委派
packagecom.learn;importjava.net.URI;importjava.nio.file.Files;importjava.nio.file.Path;importjava.nio.file.Paths;public class CustomClassLoader extendsClassLoader {
@Overridepublic Class> loadClass(String name, boolean resolve) throwsClassNotFoundException {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);
}returnc;
}
}
@Overrideprotected Class> findClass(String name) throwsClassNotFoundException {return null;
}
}
SPI:一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制
代码如下:
首先定义一个公共接口
packagecom.learn.spi.common;public interfaceIJdbc {voidconnection();
}
然后是两个实现类
packagecom.learn.spi.mysql;importcom.learn.spi.common.IJdbc;public class MysqlJdbc implementsIJdbc {
@Overridepublic voidconnection() {
System.out.println("this is MysqlJdbc...");
}
}
packagecom.learn.spi.oracle;importcom.learn.spi.common.IJdbc;public class OracleJdbc implementsIJdbc {
@Overridepublic voidconnection() {
System.out.println("this is OracleJdbc...");
}
}
最后main函数使用ServiceLoader调用
packagecom.learn.spi.gateway;importcom.learn.spi.common.IJdbc;importjava.util.ServiceLoader;public classGateWayMain {public static voidmain(String[] args) {
ServiceLoader 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
执行结果: