Java基础之SPI机制
欢迎访问陈同学博客原文
在前几天的译文 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/
),包装上写好是啥东西(接口全限定名),包装里写清楚东西放哪儿了(实现类全限定名)。
欢迎关注陈同学的公众号,一起学习,一起成长
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
python-面向对象进阶
一、isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查obj是否是cls的对象 issubclass(sub,super)检查sub是否是super的派生类 class Bar: pass class Foo(Bar): pass obj =Foo() print(isinstance(obj,Foo))#True print(issubclass(Foo,Bar))#True 二、反射 python面向对象中的反射:通过字符串的形式操作对象相关的属性 通过下面四个函数可以实现,适用于类和对象 hasattr(object,name):判断一个object中有没有name属性或者方法 getatrr(object,name,default=None):获取object中的name属性或方法,设置defult=None时没找到返回None,没设置报错 setattr(x,y,z)修改x中y的值为z,当y不存在时添加y的值为z delattr(x,y)删除x中的y属性或方法 演示代码: class BlackMed...
- 下一篇
Python学习笔记 (1)Hello World(环境搭建+输出Hello World!)
随想 高考发挥失常、科三遇火车发挥失常,各种不顺……突然发现假期都快没了,才想起高考前想象的这个假期要做的一堆事,现在来多完成一件吧。 这几篇博客仅只是我的学习笔记,凑合看吧。我这个python小白看来菜鸟教程写的是真的好。 本机环境 Lenovo E40-30,64位,赛扬N2940(四核,1.84GHz),8G内存;Ubuntu18.04(干正事)+Windows7旗舰版(娱乐) 在Ubuntu中搭建Python开发环境(Windows拿来娱乐就好) 我的Ubuntu16.04.2自带Python2.7.12(终端命令python -V 或python --version查看)。 安个最新的3.7吧。先下载源码,然后安装一些必需品—— sudo apt update; sudo apt upgrade; sudo apt dist-upgrade; sudo apt install build-essential python-dev python-setuptools python-pip python-smbus libncursesw5-dev libgdbm-dev lib...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器