虚拟机设计团队把类加载阶段中的“经过一个类的全限定名来获取描述此类的二进制字节流”这个动做放到Java虚 机外部实现,以便让应用程序本身决定如何去获取所须要的类。实现这个动做的模块称为“类加载器”。java
周志明. 深刻理解Java虚拟机:JVM高级特性与最佳实践(第2版) 机械工业出版社.spring
两个类是“相等”(包括equals、isAssignableFrom、isInstanceOf)的前提条件是这两个类的类加载器相等。sql
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException { URL jar = new URL("file:\\G:\\code\\demo\\demo-0.0.1-SNAPSHOT.jar"); URL[] urls = new URL[]{jar}; //类加载器1 URLClassLoader classLoader1 = new URLClassLoader(urls,null); Class userClass1 = classLoader1.loadClass("com.demo.User"); //类加载器2 URLClassLoader classLoader2 = new URLClassLoader(urls,null); Class userClass2 = classLoader2.loadClass("com.demo.User"); //输出false,缘由:userClass来自不一样的类加载器 System.out.println(userClass1.equals(userClass2)); }
应用程序类加载器(ApplicationClassLoader):这个类加载器由sun.misc.Launcher$App-ClassLoader实现。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者能够直接使用这个类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。数据库
周志明. 深刻理解Java虚拟机:JVM高级特性与最佳实践(第2版) 机械工业出版社tomcat
ClassLoader的结构中有一个重要的成员变量parent,也就是咱们所说的ClassLoader的双亲。微信
// java.lang.ClassLoader public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; ...
委派ClassLoader进行类加载的过程应该是:oracle
// java.lang.ClassLoader protected 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 { // 若是没有加载交给parent进行加载,若是加载成功返回类 if (parent != null) { c = parent.loadClass(name, false); } else { // 若是parent=null时,认为parent=启动类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若是parent加载失败,本身尝试加载 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; }
检查与加载过程如图所示:
app
要说明弊端,必须引入SPI。框架
SPI ,全称为 Service Provider Interface,是一种服务发现机制。JAVA中定义的SPI通常是要第三方进行实现,咱们比较常见的如:java.sql.Driver,JDK中只定义了Driver接口,并无去实现,Driver的实现由数据库厂商来实现。
oralce数据库驱动的实现以下:(来自:ojdbc6-11.2.0.4.0.jar)ide
public class OracleDriver implements Driver { ... }
同时第三方jar必须增长配置文件:
java.sql.Driver文件内容:oracle.jdbc.OracleDriver
java虚拟机经过扫描jar包下的配置文件信息加载对应接口的实现类。
定义SayHello接口
package com.demo; public interface SayHello { void hello(); }
实现SayHello接口
package com.demo; public class SayHelloImpl implements SayHello { @Override public void hello() { System.out.println("hello"); } }
在META-INF/services目录下增长com.demo.SayHello文件,文件内容为:com.demo.SayHelloImpl
主函数
public class ClassLoaderApplication { public static void main(String[] args) { ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class); for (SayHello s : sayHellos) { s.hello(); } } }
以java.sql.Driver为例,java.sql.Driver接口定义在rt.jar中,而rt.jar由BootstrapClassLoader负责加载,Driver最终由同在rt.jar包中的DriverManager类所使用,代码以下:
// class : DriverManager private static void loadInitialDrivers() { ... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); ...
因为DriverManager类在rt.jar中,因此能够认定DriverManager类最终由BootstrapClassLoader加载器负责加载,而咱们的Driver实现类(OracleDriver)通常都是由应用程序类加载器(ApplicationClassLoader)或自定义类加载器负责加载,因此Driver的实现对BootstrapClassLoader是不可见的,这样一定会致使DriverManager的loadInitialDrivers失败。
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(ThreadContextClassLoader)。这个类加载器能够经过java.lang.Thread类的setContextClassLoaser()方法进行设置,若是建立线程时还未设置,它将会从父线程中继承一个,若是在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
看下ServiceLoader的相关源码:
//class : ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service) { //ServiceLoader就是经过Thread.currentThread().getContextClassLoader()获取类加载器的 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
因此解决DriverManager类中能够加载OracleDriver的问题,能够经过将应用程序类加载器(ApplicationClassLoader)设置到java.lang.Thread类的setContextClassLoaser()方法来解决。
其实这一过程咱们基本不用本身来敲代码实现,由于咱们用的容器都已经帮咱们考虑到了。以tomcat(9.0.24)的源码为例:
//class : WebappLoader @Override public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
咱们定义的JavaBean在spring的getBean方法的建立过程其实与DriverManager建立Driver实例的过程是同样的。咱们的JavaBean是通常都是由应用程序类加载器(ApplicationClassLoader)或自定义类加载器负责加载,而Spring作为一款开源框架多是有更高层类加载器负责加载,因此Spring获取JavaBean的Class时第一优先级是经过Thread.currentThread().getContextClassLoader()来获取JavaBean的Class的类加载器。如代码所示:
//org.springframework.util.ClassUtils public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable var3) { } if (cl == null) { cl = ClassUtils.class.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable var2) { } } } return cl; }
虚拟机设计团队把类加载阶段中的“经过一个类的全限定名来获取描述此类的二进制字节流”这个动做放到Java虚 机外部实现,以便让应用程序本身决定如何去获取所须要的类。实现这个动做的模块称为“类加载器”。
周志明. 深刻理解Java虚拟机:JVM高级特性与最佳实践(第2版) 机械工业出版社.
两个类是“相等”(包括equals、isAssignableFrom、isInstanceOf)的前提条件是这两个类的类加载器相等。
public static void main(String[] args) throws ClassNotFoundException, MalformedURLException { URL jar = new URL("file:\\G:\\code\\demo\\demo-0.0.1-SNAPSHOT.jar"); URL[] urls = new URL[]{jar}; //类加载器1 URLClassLoader classLoader1 = new URLClassLoader(urls,null); Class userClass1 = classLoader1.loadClass("com.demo.User"); //类加载器2 URLClassLoader classLoader2 = new URLClassLoader(urls,null); Class userClass2 = classLoader2.loadClass("com.demo.User"); //输出false,缘由:userClass来自不一样的类加载器 System.out.println(userClass1.equals(userClass2)); }
应用程序类加载器(ApplicationClassLoader):这个类加载器由sun.misc.Launcher$App-ClassLoader实现。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者能够直接使用这个类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。
周志明. 深刻理解Java虚拟机:JVM高级特性与最佳实践(第2版) 机械工业出版社
ClassLoader的结构中有一个重要的成员变量parent,也就是咱们所说的ClassLoader的双亲。
// java.lang.ClassLoader public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; ...
委派ClassLoader进行类加载的过程应该是:
// java.lang.ClassLoader protected 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 { // 若是没有加载交给parent进行加载,若是加载成功返回类 if (parent != null) { c = parent.loadClass(name, false); } else { // 若是parent=null时,认为parent=启动类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若是parent加载失败,本身尝试加载 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; }
检查与加载过程如图所示:
要说明弊端,必须引入SPI。
SPI ,全称为 Service Provider Interface,是一种服务发现机制。JAVA中定义的SPI通常是要第三方进行实现,咱们比较常见的如:java.sql.Driver,JDK中只定义了Driver接口,并无去实现,Driver的实现由数据库厂商来实现。
oralce数据库驱动的实现以下:(来自:ojdbc6-11.2.0.4.0.jar)
public class OracleDriver implements Driver { ... }
同时第三方jar必须增长配置文件:
java.sql.Driver文件内容:oracle.jdbc.OracleDriver
java虚拟机经过扫描jar包下的配置文件信息加载对应接口的实现类。
定义SayHello接口
package com.demo; public interface SayHello { void hello(); }
实现SayHello接口
package com.demo; public class SayHelloImpl implements SayHello { @Override public void hello() { System.out.println("hello"); } }
在META-INF/services目录下增长com.demo.SayHello文件,文件内容为:com.demo.SayHelloImpl
主函数
public class ClassLoaderApplication { public static void main(String[] args) { ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class); for (SayHello s : sayHellos) { s.hello(); } } }
以java.sql.Driver为例,java.sql.Driver接口定义在rt.jar中,而rt.jar由BootstrapClassLoader负责加载,Driver最终由同在rt.jar包中的DriverManager类所使用,代码以下:
// class : DriverManager private static void loadInitialDrivers() { ... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); ...
因为DriverManager类在rt.jar中,因此能够认定DriverManager类最终由BootstrapClassLoader加载器负责加载,而咱们的Driver实现类(OracleDriver)通常都是由应用程序类加载器(ApplicationClassLoader)或自定义类加载器负责加载,因此Driver的实现对BootstrapClassLoader是不可见的,这样一定会致使DriverManager的loadInitialDrivers失败。
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(ThreadContextClassLoader)。这个类加载器能够经过java.lang.Thread类的setContextClassLoaser()方法进行设置,若是建立线程时还未设置,它将会从父线程中继承一个,若是在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
看下ServiceLoader的相关源码:
//class : ServiceLoader public static <S> ServiceLoader<S> load(Class<S> service) { //ServiceLoader就是经过Thread.currentThread().getContextClassLoader()获取类加载器的 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
因此解决DriverManager类中能够加载OracleDriver的问题,能够经过将应用程序类加载器(ApplicationClassLoader)设置到java.lang.Thread类的setContextClassLoaser()方法来解决。
其实这一过程咱们基本不用本身来敲代码实现,由于咱们用的容器都已经帮咱们考虑到了。以tomcat(9.0.24)的源码为例:
//class : WebappLoader @Override public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
咱们定义的JavaBean在spring的getBean方法的建立过程其实与DriverManager建立Driver实例的过程是同样的。咱们的JavaBean是通常都是由应用程序类加载器(ApplicationClassLoader)或自定义类加载器负责加载,而Spring作为一款开源框架多是有更高层类加载器负责加载,因此Spring获取JavaBean的Class时第一优先级是经过Thread.currentThread().getContextClassLoader()来获取JavaBean的Class的类加载器。如代码所示:
//org.springframework.util.ClassUtils public static ClassLoader getDefaultClassLoader() { ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable var3) { } if (cl == null) { cl = ClassUtils.class.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable var2) { } } } return cl; }
更多spring源码相关知识点击
《超哥spring源码解析之核心容器篇》免费视频学习
也能够关注超哥微信公众号: