双亲委派模型以及SpringFactoriesLoader详解(最全最简单的介绍)
文章目录
- 前言
- 类加载的过程
- 类加载器
- 何为双亲委派模型
- ClassLoader类的loadClass方法
- 双亲委派模型存在的问题
- 解决办法
- 以JDBC驱动管理为例
- 加载资源
- SpringFactoriesLoader详解
- 总结
前言
前面我们介绍了JavaConfig和常用的Annotation,这一篇文章我们来聊聊SpringFactoriesLoader
,在讲SpringFactoriesLoader
之前我会先说到JVM的类加载器以及双亲委派模型。闲话少叙,直入主题。
类加载的过程
大致的步骤分为如下几步:
- 加载:使用类加载器从不同的地方加载二进制流到方法区
- 校验:为了确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求。
- 准备:在方法区为静态变量分配内存,并初始化默认值
- 解析:将符号引用替换成直接引用,(符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。直接引用可以是 1. 直接指向目标的指针、类变量、类方法;2、相对偏移量;3、一个能间接定位到目标的句柄。)
- 初始化:根据静态变量的赋值语法和静态代码块语法,生成一个初始化方法并执行。
类加载器
JVM一共有三种类加载器,分别是:
- 启动类加载器(
BootstrapClassLoader
)加载Java核心类库(%java.home%lib下面的核心类库 或 -Xbootclasspath选项指定的jar包); - 扩展类加载器(
ExtClassLoader
)加载扩展类库(%java.home%/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 ); - 应用类加载器(
AppClassLoader
)加载应用的类路径(用户类路径(java -classpath或-Djava.class.path变量所指的目录)下的类库。
类的继承关系如下图所示:
JVM通过双亲委派模型进行类的加载,我们可以通过继承java.lang.classLoader
实现自己的类加载器。
何为双亲委派模型
当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终的加载任务都会传递到最顶层的BootstrapClassLoader
(启动类加载器),只有当父加载器无法完成加载任务时,才会尝试自己来加载。事实上,大多数情况下,越基础的类由越上层的加载器进行加载。
其加载流程图如下:
下面就是ClassLoader类的loadClass方法
ClassLoader类的loadClass方法
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded//首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回。Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//遵循双亲委派的模型,首先通过递归从父加载器开始找//直到父类加载器是BootstrapClassLoader为止if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) { }if (c == null) {// If still not found, then invoke findClass in order// to find the class.//如果还找不到,则尝试通过findClass方法去寻找//findClass是留给开发者自己实现的,也就是说自定义类加载器时,//重写此方法即可。c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}}
采用双亲委派的一个好处主要有如下两点:
- 防止类被重复加载
Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层次关系可以避免类被重复加载, 当父类已经加载了该类时,子类就不会再加载一次。保证了使用不同类加载器最终得到的是同一个对象。 - 保证核心库的类型安全
Java核心api中定义的类不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
双亲委派模型存在的问题
使用双亲委派模型也存在一些问题,例如:Java提供了很多服务提供者接口(ServiceProvinderInterface,SPI),允许第三方为这些接口提供实现,常见的SPI有JDBC,JNDI等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在了一个问题:SPI的接口是Java核心库的一部分,是由BootStrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootStrapClassLoader是无法找到SPI的实现类的。因为它只加载Java的核心库,它不能代理给AppClassLoader,因为他是最顶层的类加载器,也就是说,双亲委派模型并不能解决这个问题。那么如何解决这个问题呢?
解决办法
线程上下文加载器(ContextClassLoader)正好解决了这个问题。从名称上看,可能会误解为它是一种新的类加载器,实际上,它仅仅是Thread类的一个变量而已,可以通过setContextClassLoader(ClassLoadercl) 和getContextClassLoader()来设置和获取该对象,如果不做任何的设置。Java应用的线程上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器。就可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。
以JDBC驱动管理为例
mysql-connector-java-6.0.6.jar
下的META-INF/services
目录下有一个以 接口全限定名 (java.sql.Driver)为命名的文件,内容为实现类的全限定名。
主程序通过java.util.ServiceLoader
动态装载实现模块,它通过扫描META-INF/services
目录下的配置文件找到实现类的全限定名,把类加载到JVM中。需要注意的是SPI的实现类必须携带一个不带参数的构造方法,用于反射生成实例。如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {//// Register ourselves with the DriverManager//static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}/*** Construct a new driver and register it with DriverManager* */public Driver() throws SQLException {// Required for Class.forName().newInstance()}
}
ServiceLoader 类装载实现模块的代码如下:
public final class ServiceLoader<S>implements Iterable<S>
{private static final String PREFIX = "META-INF/services/";// The class loader used to locate, load, and instantiate providersprivate final ClassLoader loader;/*** Creates a new service loader for the given service type, using the* current thread's {@linkplain java.lang.Thread#getContextClassLoader* context class loader}.* * */public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}
}
加载资源
类加载器除了加载Class外,还有一个非常重要的功能,就是加载资源,
它可以从jar包中读取任何资源文件,比如:ClassLoader.getResource(String name)
方法就是用于读取jar包中的资源文件。
public Enumeration<URL> getResources(String name) throws IOException {Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];if (parent != null) {tmp[0] = parent.getResources(name);} else {tmp[0] = getBootstrapResources(name);}tmp[1] = findResources(name);return new CompoundEnumeration<>(tmp);}
它的逻辑其实跟类加载的逻辑是一样的。首先判断父类加载器是否为空,如果不为空则委托父类加载器执行资源查找任务,直到到达BootstrapClassLoader
,只有当父类加载器找不到时,最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的jar包。就如同加载class一样,最后会扫描所有的jar包,找到符合条件的资源文件。findResources(name)
方法会遍历其负责加载的所有jar包。找到jar包中名称为name的资源文件,这里的资源可以是任何文件,甚至是.class文件。比如下面的实例:用于查找String.class文件。
//寻找String.class文件public static void main(String[] args) throws IOException {String name = "java/lang/String.class";Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);while (urls.hasMoreElements()) {URL url = urls.nextElement();System.out.println(url.toString());}}
运行得到如下结果:
$JAVA_HOME/jre/lib/rt.jar!/java/lang/String.class
SpringFactoriesLoader详解
说完了类加载器,以及双亲委派模型还有资源文件的查找,下面就开始介绍我们本篇文章的真正主角,SpringFactoriesLoader
它本质上属于Spring框架私有的一种扩展方案,类似于SPI,Spring Boot在Spring基础上的很多核心的功能都是基于此。根据资源文件的URL,就可以构造相应的文件来读取资源内容。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";//spring.factories文件的格式为:key=value1,value2,value3//从所有的jar中找到META-INF/spring.factories文件//然后,从文件中解析出key=factoryClass类名称的所有value值public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {String factoryClassName = factoryClass.getName();//获取资源文件的URLEnumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));List<String> result = new ArrayList<String>();//遍历所有的URLwhile (urls.hasMoreElements()) {URL url = urls.nextElement();//根据资源文件URL解析properties文件Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));String factoryClassNames = properties.getProperty(factoryClassName); //组装并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));}return result;}}
有了前面关于ClassLoader的知识铺垫,再来看上面的代码就简单了。首先从classpath下每个jar包下搜寻文件名是META-INF/spring.factories
的配置文件,然后将解析properties文件,找到指定名称的配置后返回,需要注意的是,这里不仅仅是在classpath路径下查找,会扫描所有路径下的jar包,只不过这个文件只会在classpath下的jar包中。简单看下spring.factories吧。
// 来⾃ org.springframework.boot.autoconfigure下的META-INF/spring.factories
//EnableAutoConfiguration后文会讲到,它用于开启Spring Boot自动配置功能
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
执行loadFactoryNames((EnableAutoConfiguration.class,classLoader)
后,得到对应的一组@Configuration
类,我们就可以通过反射实例化这些类然后注入到IOC容器中,最后容器里就有了一系列标注了@Cofiguration
的JavaConfig形式的配置类。
总结
本文首先介绍了JVM中的三种类加载器,分别是启动类加载器,扩展类加载器,以及应用类加载器。然后说到了双亲委派模型以及它的缺点。根据它的缺点引出了线程上下文加载器(ContextClassLoader) 以及他在SPI的实现上的运用。最后就是详细介绍了SpringFactoriesLoader的实现原理。
双亲委派模型以及SpringFactoriesLoader详解(最全最简单的介绍)相关推荐
- 双亲委派模型和破坏性双亲委派模型详解
从JVM的角度来看,只存在两种类加载器: 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录 ...
- 深入理解什么是双亲委派模型(Java图文详解)
[辰兮要努力]:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行! 博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端.后台.数据 ...
- Java双亲委派模型:为什么要双亲委派?如何打破它?破在哪里?
文章目录 一.前言 二.类加载器 三.双亲委派机制 1.什么是双亲委派 2.为什么要双亲委派? 四.破坏双亲委派 1.直接自定义类加载器加载 2.跳过AppClassLoader和ExtClassLo ...
- 分析JVM双亲委派模型的类加载源码 自定义类加载器
双亲委派模型下,在父类加载器无法加载的情况下再由当前类加载器去加载.具体的实现逻辑在java.util.ClassLoader抽象类的loadClass方法中.在该方法中,先检查是否已经加载过,如果没 ...
- ClassLoader的双亲委派模型
类加载器用以实现类加载功能,并且确定被加载的类在Java虚拟机中的唯一性. 注意:由同一个类加载器加载,则这两个类相等,由不同的类加载器加载,则这两个类不相等. 我们知道类加载器主要分为: 启动类加载 ...
- java 打破双亲委派,为什么说java spi破坏双亲委派模型?
虽然有SPI破坏双亲委派模型的说法,但我不太认同.简单说下. 双亲委派模型(再次吐槽下这个翻译),是一种加载类的约定.这个约定的一个用处是保证安全.比如说你写Java用了String类,你怎么保证你用 ...
- jvm:类加载器与双亲委派模型
两个类相等需要类本身相等,并且使用同一个类加载器进行加载.这是因为每一个类加载器都拥有一个独立的类名称空间. 这里的相等,包括类的 Class 对象的 equals() 方法.isAssignable ...
- amba simple class驱动_学习笔记:class加载器和双亲委派模型
类加载器 类加载器有四种 启动类加载器(Bootstrap ClassLoader) 负责加载 JAVA_HOMElib ⽬录中的,或通过-Xbootclasspath参数指定路径中的且被虚拟机认可( ...
- 面向对象回顾(静态变量、类加载机制/双亲委派模型、Object类的方法、类和对象区别)
1. 静态变量存在什么位置? 方法区 2. 类加载机制,双亲委派模型,好处是什么? 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务, ...
最新文章
- OpenCV移植各向异性图像分割间隙流体的实例(附完整代码)
- 系统在iis6上部署
- linux下忘记mysql root密码解决办法
- 使用recyclerView实现无限循环banner效果
- 荷兰商业银行使用精益领导力推行改进
- python生成json_python中如何进行json转化
- 在pycharm/IDEA里编辑latex:TeXiFy-IDEA/Pycharm
- Reveal查看任意app的高级技巧
- JavaScript的Array对象使用(1)
- paip.目录文件列表排序算法
- python颜色参数_python matplotlib:plt.scatter() 大小和颜色参数详解
- EXE反编译方法及工具
- Windows自动关机命令
- 渗透工具-masscan
- Stata:何时使用线性概率模型而非Logit?
- python字体设置不了_设置字体样式
- html 给word插入页眉和页脚,如何在Word插入页眉和页脚
- Ice helloworld
- system libzip must be upgraded to version >= 0.11
- Tic tac toe井字棋游戏python实现