前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java基础之SPI机制

Java基础之SPI机制

原创
作者头像
码代码的陈同学
发布2018-08-23 22:09:59
6010
发布2018-08-23 22:09:59
举报

欢迎访问陈同学博客原文

在前几天的译文 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项目,创建以下对象。

代码语言:txt
复制
// Animal接口, 制定了 sing() 标准
public interface Animal {
    void sing();
}

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

Cat.java

代码语言:txt
复制
public class Cat implements Animal {
    public void sing() {
        System.out.println("喵~");
    }
}

Cuckoo.java

代码语言:txt
复制
public class Cuckoo implements Animal {
    public void sing() {
        System.out.println("布谷~");
    }
}

Dog.java

代码语言:txt
复制
public class Dog implements Animal {
    public void sing() {
        System.out.println("汪~");
    }
}

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

代码语言:txt
复制
org.utopiavip.spi.Cat
org.utopiavip.spi.Dog
org.utopiavip.spi.Cuckoo

将项目打成jar包。

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

代码语言:txt
复制
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();
        }
    }
}

运行后输出如下:

代码语言:txt
复制
喵~
汪~
布谷~

小例子就完成了。

Mysql驱动Demo

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

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

代码语言:txt
复制
System.out.println(java.sql.Driver.class.getClassLoader());
System.out.println(com.mysql.fabric.jdbc.FabricMySQLDriver.class.getClassLoader());

输出结果如下:

代码语言:txt
复制
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负责加载。

代码语言:txt
复制
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。

代码语言:txt
复制
public void setContextClassLoader(ClassLoader cl) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setContextClassLoader"));
    }
    contextClassLoader = cl;
}

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

代码语言:txt
复制
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;

小结

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


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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是SPI?
  • SPI HelloWorld
  • Mysql驱动Demo
  • ContextClassLoader
  • 小结
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档