文章目录

  • 前言
  • 类加载的过程
  • 类加载器
  • 何为双亲委派模型
    • ClassLoader类的loadClass方法
  • 双亲委派模型存在的问题
    • 解决办法
      • 以JDBC驱动管理为例
    • 加载资源
    • SpringFactoriesLoader详解
  • 总结

前言

前面我们介绍了JavaConfig和常用的Annotation,这一篇文章我们来聊聊SpringFactoriesLoader,在讲SpringFactoriesLoader之前我会先说到JVM的类加载器以及双亲委派模型。闲话少叙,直入主题。

类加载的过程

大致的步骤分为如下几步:

  1. 加载:使用类加载器从不同的地方加载二进制流到方法区
  2. 校验:为了确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求。
  3. 准备:在方法区为静态变量分配内存,并初始化默认值
  4. 解析:将符号引用替换成直接引用,(符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。直接引用可以是 1. 直接指向目标的指针、类变量、类方法;2、相对偏移量;3、一个能间接定位到目标的句柄。)
  5. 初始化:根据静态变量的赋值语法和静态代码块语法,生成一个初始化方法并执行。

类加载器

JVM一共有三种类加载器,分别是:

  1. 启动类加载器(BootstrapClassLoader)加载Java核心类库(%java.home%lib下面的核心类库 或 -Xbootclasspath选项指定的jar包);
  2. 扩展类加载器(ExtClassLoader)加载扩展类库(%java.home%/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 );
  3. 应用类加载器(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;}}

采用双亲委派的一个好处主要有如下两点:

  1. 防止类被重复加载
    Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层次关系可以避免类被重复加载, 当父类已经加载了该类时,子类就不会再加载一次。保证了使用不同类加载器最终得到的是同一个对象。
  2. 保证核心库的类型安全
    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详解(最全最简单的介绍)相关推荐

  1. 双亲委派模型和破坏性双亲委派模型详解

    从JVM的角度来看,只存在两种类加载器: 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录 ...

  2. 深入理解什么是双亲委派模型(Java图文详解)

    [辰兮要努力]:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行! 博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端.后台.数据 ...

  3. Java双亲委派模型:为什么要双亲委派?如何打破它?破在哪里?

    文章目录 一.前言 二.类加载器 三.双亲委派机制 1.什么是双亲委派 2.为什么要双亲委派? 四.破坏双亲委派 1.直接自定义类加载器加载 2.跳过AppClassLoader和ExtClassLo ...

  4. 分析JVM双亲委派模型的类加载源码 自定义类加载器

    双亲委派模型下,在父类加载器无法加载的情况下再由当前类加载器去加载.具体的实现逻辑在java.util.ClassLoader抽象类的loadClass方法中.在该方法中,先检查是否已经加载过,如果没 ...

  5. ClassLoader的双亲委派模型

    类加载器用以实现类加载功能,并且确定被加载的类在Java虚拟机中的唯一性. 注意:由同一个类加载器加载,则这两个类相等,由不同的类加载器加载,则这两个类不相等. 我们知道类加载器主要分为: 启动类加载 ...

  6. java 打破双亲委派,为什么说java spi破坏双亲委派模型?

    虽然有SPI破坏双亲委派模型的说法,但我不太认同.简单说下. 双亲委派模型(再次吐槽下这个翻译),是一种加载类的约定.这个约定的一个用处是保证安全.比如说你写Java用了String类,你怎么保证你用 ...

  7. jvm:类加载器与双亲委派模型

    两个类相等需要类本身相等,并且使用同一个类加载器进行加载.这是因为每一个类加载器都拥有一个独立的类名称空间. 这里的相等,包括类的 Class 对象的 equals() 方法.isAssignable ...

  8. amba simple class驱动_学习笔记:class加载器和双亲委派模型

    类加载器 类加载器有四种 启动类加载器(Bootstrap ClassLoader) 负责加载 JAVA_HOMElib ⽬录中的,或通过-Xbootclasspath参数指定路径中的且被虚拟机认可( ...

  9. 面向对象回顾(静态变量、类加载机制/双亲委派模型、Object类的方法、类和对象区别)

    1. 静态变量存在什么位置? 方法区 2. 类加载机制,双亲委派模型,好处是什么? 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务, ...

最新文章

  1. OpenCV移植各向异性图像分割间隙流体的实例(附完整代码)
  2. 系统在iis6上部署
  3. linux下忘记mysql root密码解决办法
  4. 使用recyclerView实现无限循环banner效果
  5. 荷兰商业银行使用精益领导力推行改进
  6. python生成json_python中如何进行json转化
  7. 在pycharm/IDEA里编辑latex:TeXiFy-IDEA/Pycharm
  8. Reveal查看任意app的高级技巧
  9. JavaScript的Array对象使用(1)
  10. paip.目录文件列表排序算法
  11. python颜色参数_python matplotlib:plt.scatter() 大小和颜色参数详解
  12. EXE反编译方法及工具
  13. Windows自动关机命令
  14. 渗透工具-masscan
  15. Stata:何时使用线性概率模型而非Logit?
  16. python字体设置不了_设置字体样式
  17. html 给word插入页眉和页脚,如何在Word插入页眉和页脚
  18. Ice helloworld
  19. system libzip must be upgraded to version >= 0.11
  20. Tic tac toe井字棋游戏python实现

热门文章

  1. 时间Date类型如何比较大小
  2. 1.1 计算机语言发展史以及未来方向
  3. fileupload.class.php,PHP 文件上传类 FileUpload 高洛峰老师 细说PHP
  4. 100本最棒的web前端图书推荐
  5. 【Redis】Redis数据库
  6. 用C语言做了一个外卖管理系统
  7. 基于DEM的GIS水文分析——河网与集水区域的提取
  8. 一文让你秒懂存储虚拟化
  9. 苹果8wifi找不到服务器,教大家遇到苹果手机iPhone11搜不到无线网怎么解决
  10. visual studio code打开预览.md文件