最近重温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
执行结果: