Java基础之SPI机制

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: Java基础之SPI机制与ContextClassLoader

欢迎访问陈同学博客原文

在前几天的译文 Java中的类加载器 中有部分关于ContextClassLoader的内容,涉及到SPI机制,本文将学习下相关知识。

什么是SPI?

SPI全称为 Service Provider Interface,直译为 服务提供者接口,翻译成中文后比较拗口,难以理解。

简单来说,SPI通过将服务的接口与实现分离以实现解耦,提高程序拓展性的机制,达到插拔式的效果。相同的标准,各服务厂商可以提供不同的实现。这尤其适合于面对未知的实现或者对拓展开放的系统,可以先行制定标准,服务提供者根据标准提供实现即可。

Java中使用SPI机制的例子很多,例举几个:

  • 数据库驱动 ( java.sql.Driver ),各数据库厂商(Mysql、Oracle等)可以遵守规范独立开发自己的驱动
  • Servlet容器初始化接口( javax.servlet.ServletContainerInitializer ),Tomcat提供了实现
  • Apache common-logging 中提供的日志接口,许多日志框架做了实现

稍微延伸一下,其实不仅仅是Java,像计算机行业的各种规范、协议也是类似的。甚至生活中的例子,如:

  • 命题作文:设立标题,大家各自发挥成文
  • 手机壳:根据手机尺寸标准,实现各式各样的手机壳
  • 喜剧:以逗笑观众为标准,各表演者以不同的作品与形式为观众送去欢乐

扯的有点远,下面以一个简单例子演示下。

SPI HelloWorld

首先,了解下SPI机制的约定(约定优于配置理念):

  • META-INF/services/ 目录下创建一个以 接口全限定名 命名的文件,文件内容为 实现类的全限定名
  • 使用 java.util.ServiceLoader 来动态加载 META-INF/services/ 下的实现类
  • 实现类必须有一个无参构造器

假设森林动物园举行歌唱比赛,各参赛动物选手需高歌一曲。我们定义一个接口 Animal,标准为 sing() 唱歌。

创建一个普通maven项目,创建以下对象。

// Animal接口, 制定了 sing() 标准
public interface Animal {
    void sing();
}

三位参赛选手,分别实现了sing() 标准

Cat.java

public class Cat implements Animal {
    public void sing() {
        System.out.println("喵~");
    }
}

Cuckoo.java

public class Cuckoo implements Animal {
    public void sing() {
        System.out.println("布谷~");
    }
}

Dog.java

public class Dog implements Animal {
    public void sing() {
        System.out.println("汪~");
    }
}

在resource下创建META-INF/services目录,下面创建以接口全限定名org.utopiavip.spi.Animal命名的文件,内容为三位实现者:

org.utopiavip.spi.Cat
org.utopiavip.spi.Dog
org.utopiavip.spi.Cuckoo

将项目打成jar包。

在另一个项目中引入该jar包,测试类如下:

public class SpiDemo {

    public ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);

    public static void main(String args[]) {
        SpiDemo spiDemo = new SpiDemo();
        spiDemo.sing();
    }

    public void sing() {
        for (Animal singer : serviceLoader) {
            singer.sing();
        }
    }
}

运行后输出如下:

喵~
汪~
布谷~

小例子就完成了。

Mysql驱动Demo

Mysql驱动包中对 java.sql.Driver 的实现类为 com.mysql.fabric.jdbc.FabricMySQLDriver

再看看接口和实现类的ClassLoader。

System.out.println(java.sql.Driver.class.getClassLoader());
System.out.println(com.mysql.fabric.jdbc.FabricMySQLDriver.class.getClassLoader());

输出结果如下:

null
sun.misc.Launcher$AppClassLoader@135fbaa4

null表示Bootstrap class loader(SPI的接口都由Bootstrap class loader加载),而实现类是由AppClassLoader加载的。

ContextClassLoader

类加载规则中有这么一点:一个类中所关联的其他类都由当前类的加载器进行加载

仍然以Driver为例,Java中使用DriverManager来获取JDBC连接,DriverManager 位于 rt.jar 中,由Bootstrap class loader负责加载。

java.sql.DriverManager.getConnection("url", "user", "pwd")

在getConnection()的调用过程中,需要加载 java.sql.Driver 的实现类 com.mysql.fabric.jdbc.FabricMySQLDriver,可Bootstrap class loader无法找到该实现类,因为FabricMySQLDriver由System class loader加载。

这是由于类加载的委派原则及可见性制约,Bootstrap class loader将无法获取子加载器System class loader中加载的FabricMySQLDriver类。

为了解决这个问题,提出了 ContextClassLoader 概念,绕开委派原则,既然当前的加载器是Bootstrap class loader,导致无法加载FabricMySQLDriver类,那就变更当前的class loader,想加载谁就加载谁!虽然有点流氓派头,但确实是这么干的。规则是人定的,变更规则成本太高,就搞点特殊化。

java.lang.Thread 有个NB的方法 setContextClassLoader(),用来变更当前线程的class loader。

public void setContextClassLoader(ClassLoader cl) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setContextClassLoader"));
    }
    contextClassLoader = cl;
}

contextClassLoader 取名也很有趣,当前线程的 context ClassLoader。特殊化也就搞一小会,不大范围搞。

/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;

小结

BB这么多,SPI其实非常简单:大佬们定规矩(规范),兄弟们实现后放到约定的地方(META-INF/service/),包装上写好是啥东西(接口全限定名),包装里写清楚东西放哪儿了(实现类全限定名)。


欢迎关注陈同学的公众号,一起学习,一起成长

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
Java
Java并发编程中的锁机制
【2月更文挑战第22天】 在Java并发编程中,锁机制是一种重要的同步手段,用于保证多个线程在访问共享资源时的安全性。本文将介绍Java锁机制的基本概念、种类以及使用方法,帮助读者深入理解并发编程中的锁机制。
|
1月前
|
Java 程序员
Java中的异常处理机制
【2月更文挑战第22天】在Java编程中,异常处理是一个重要的概念。它允许程序员在程序执行过程中遇到错误时,对错误进行处理,而不是让程序崩溃。本文将介绍Java中的异常处理机制,包括异常的分类、如何捕获和处理异常以及自定义异常等内容。
18 1
|
1月前
|
存储 Java 数据库
|
1月前
|
Java
深入了解Java中的锁机制
深入了解Java中的锁机制
|
1月前
|
Java 程序员 编译器
认识Java 的反射机制
反射Reflection被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。
29 5
|
29天前
|
开发框架 Java API
java反射机制的原理与简单使用
java反射机制的原理与简单使用
17 1
|
17天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
21天前
|
搜索推荐 Java
Java基础(快速排序算法)
Java基础(快速排序算法)
23 4
|
22天前
|
Java 程序员 开发者
深入理解Java异常处理机制
在Java编程中,异常处理是确保程序健壮性与稳定性的重要组成部分。本文旨在深度剖析Java异常处理机制的核心概念、结构及其实际应用策略,帮助开发者更好地理解并运用异常处理来优化程序设计。我们将从Java异常体系结构入手,探讨try-catch-finally语句块的执行流程,分析自定义异常的必要性与实现方式,并通过实例演示如何有效地管理和处理异常情况。
23 3
|
29天前
|
设计模式 XML 存储
java中的反射机制
java中的反射机制
12 1

热门文章

最新文章