阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

本篇文章将从深入理解java SPI机制来介绍组件化框架设计:

一、SPI机制定义

SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。

二、典型实例:jdbc的设计

通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。
Mysql的则是com.mysql.jdbc.Drive,Oracle则是oracle.jdbc.driver.OracleDriver。

 
 

伪代码如下:

  1. //注:从jdbc4.0之后无需这个操作,spi机制会自动找到相关的驱动实现
  2. //Class.forName(driver);
  3. //1.getConnection()方法,连接MySQL数据库。有可能注册了多个Driver,这里通过遍历成功连接后返回。
  4. con = DriverManager.getConnection(mysqlUrl,user,password);
  5. //2.创建statement类对象,用来执行SQL语句!!
  6. Statement statement = con.createStatement();
  7. //3.ResultSet类,用来存放获取的结果集!!
  8. ResultSet rs = statement.executeQuery(sql);

jdbc连接源码分析

  1. java.sql.DriverManager静态块初始执行,其中使用spi机制加载jdbc具体实现
  1. //java.sql.DriverManager.java
  2. //当调用DriverManager.getConnection(..)时,static会在getConnection(..)执行之前被触发执行
  3. /**
  4. * Load the initial JDBC drivers by checking the System property
  5. * jdbc.properties and then use the {@code ServiceLoader} mechanism
  6. */
  7. static {
  8. loadInitialDrivers();
  9. println("JDBC DriverManager initialized");
  10. }

2.loadInitialDrivers()中完成了引入的数据库驱动的查找以及载入,本示例只引入了oracle厂商的mysql,我们具体看看。

//java.util.serviceLoader.java

  1. private static void loadInitialDrivers() {
  2. String drivers;
  3. try {
  4. drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
  5. public String run() {
  6. //使用系统变量方式加载
  7. return System.getProperty("jdbc.drivers");
  8. }
  9. });
  10. } catch (Exception ex) {
  11. drivers = null;
  12. }
  13. //如果spi 存在将使用spi方式完成提供的Driver的加载
  14. // If the driver is packaged as a Service Provider, load it.
  15. // Get all the drivers through the classloader
  16. // exposed as a java.sql.Driver.class service.
  17. // ServiceLoader.load() replaces the sun.misc.Providers()
  18. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  19. public Void run() {
  20. //查找具体的provider,就是在META-INF/services/***.Driver文件中查找具体的实现。
  21. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  22. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  23. /* Load these drivers, so that they can be instantiated.
  24. * It may be the case that the driver class may not be there
  25. * i.e. there may be a packaged driver with the service class
  26. * as implementation of java.sql.Driver but the actual class
  27. * may be missing. In that case a java.util.ServiceConfigurationError
  28. * will be thrown at runtime by the VM trying to locate
  29. * and load the service.
  30. *
  31. * Adding a try catch block to catch those runtime errors
  32. * if driver not available in classpath but it's
  33. * packaged as service and that service is there in classpath.
  34. */
  35. //查找具体的实现类的全限定名称
  36. try{
  37. while(driversIterator.hasNext()) {
  38. driversIterator.next();//加载并初始化实现类
  39. }
  40. } catch(Throwable t) {
  41. // Do nothing
  42. }
  43. return null;
  44. }
  45. });
  46. println("DriverManager.initialize: jdbc.drivers = " + drivers);
  47. if (drivers == null || drivers.equals("")) {
  48. return;
  49. }
  50. String[] driversList = drivers.split(":");
  51. ....
  52. }
  53. }

3.java.util.ServiceLoader 加载spi实现类.

上一步的核心代码如下,我们接着分析:


  1. //java.util.serviceLoader.java
  2. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  3. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  4. try{
  5. //查找具体的实现类的全限定名称
  6. while(driversIterator.hasNext()) {
  7. //加载并初始化实现
  8. driversIterator.next();
  9. }
  10. } catch(Throwable t) {
  11. // Do nothing
  12. }

主要是通过ServiceLoader来完成的,我们按照执行顺序来看看ServiceLoader实现:

  1. //初始化一个ServiceLoader,load参数分别是需要加载的接口class对象,当前类加载器
  2. public static <S> ServiceLoader<S> load(Class<S> service) {
  3. ClassLoader cl = Thread.currentThread().getContextClassLoader();
  4. return ServiceLoader.load(service, cl);
  5. }
  6. public static <S> ServiceLoader<S> load(Class<S> service,
  7. ClassLoader loader)
  8. {
  9. return new ServiceLoader<>(service, loader);
  10. }

遍历所有存在的service实现

  1. public boolean hasNext() {
  2. if (acc == null) {
  3. return hasNextService();
  4. } else {
  5. PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
  6. public Boolean run() { return hasNextService(); }
  7. };
  8. return AccessController.doPrivileged(action, acc);
  9. }
  10. }

  1. //写死的一个目录
  2. private static final String PREFIX = "META-INF/services/";
  3. private boolean hasNextService() {
  4. if (nextName != null) {
  5. return true;
  6. }
  7. if (configs == null) {
  8. try {
  9. String fullName = PREFIX + service.getName();
  10. //通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名
  11. if (loader == null)
  12. configs = ClassLoader.getSystemResources(fullName);
  13. else
  14. configs = loader.getResources(fullName);
  15. } catch (IOException x) {
  16. fail(service, "Error locating configuration files", x);
  17. }
  18. }
  19. //判断是否读取到实现类全限定名,比如mysql的“com.mysql.jdbc.Driver

  20. while ((pending == null) || !pending.hasNext()) {
  21. if (!configs.hasMoreElements()) {
  22. return false;
  23. }
  24. pending = parse(service, configs.nextElement());
  25. }
  26. nextName = pending.next();//nextName保存,后续初始化实现类使用
  27. return true;//查到了 返回true,接着调用next()
  28. }

  1. public S next() {
  2. if (acc == null) {//用来判断serviceLoader对象是否完成初始化
  3. return nextService();
  4. } else {
  5. PrivilegedAction<S> action = new PrivilegedAction<S>() {
  6. public S run() { return nextService(); }
  7. };
  8. return AccessController.doPrivileged(action, acc);
  9. }
  10. }
  11. private S nextService() {
  12. if (!hasNextService())
  13. throw new NoSuchElementException();
  14. String cn = nextName;//上一步找到的服务实现者全限定名
  15. nextName = null;
  16. Class<?> c = null;
  17. try {
  18. //加载字节码返回class对象.但并不去初始化(换句话就是说不去执行这个类中的static块与static变量初始化)
  19. //
  20. c = Class.forName(cn, false, loader);
  21. } catch (ClassNotFoundException x) {
  22. fail(service,
  23. "Provider " + cn + " not found");
  24. }
  25. if (!service.isAssignableFrom(c)) {
  26. fail(service,
  27. "Provider " + cn + " not a subtype");
  28. }
  29. try {
  30. //初始化这个实现类.将会通过static块的方式触发实现类注册到DriverManager(其中组合了一个CopyOnWriteArrayList的registeredDrivers成员变量)中
  31. S p = service.cast(c.newInstance());
  32. providers.put(cn, p);//本地缓存 (全限定名,实现类对象)
  33. return p;
  34. } catch (Throwable x) {
  35. fail(service,
  36. "Provider " + cn + " could not be instantiated",
  37. x);
  38. }
  39. throw new Error(); // This cannot happen
  40. }

上一步中,Sp = service.cast(c.newInstance()) 将会导致具体实现者的初始化,比如mysqlJDBC,会触发如下代码:

  1. //com.mysql.jdbc.Driver.java
  2. ......
  3. private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
  4. ......
  5. static {
  6. try {
  7. //并发安全的想一个copyOnWriteList中方
  8. java.sql.DriverManager.registerDriver(new Driver());
  9. } catch (SQLException E) {
  10. throw new RuntimeException("Can't register driver!");
  11. }
  12. }

4.最终Driver全部注册并初始化完毕,开始执行DriverManager.getConnection(url, “root”, “root”)方法并返回。

使用实例
四个项目:spiInterface、spiA、spiB、spiDemo

spiInterface中定义了一个com.zs.IOperation接口。

spiA、spiB均是这个接口的实现类,服务提供者。

spiDemo作为客户端,引入spiA或者spiB依赖,面向接口编程,通过spi的方式获取具体实现者并执行接口方法。

  1. ├─spiA
  2. └─src
  3. ├─main
  4. ├─java
  5. └─com
  6. └─zs
  7. ├─resources
  8. └─META-INF
  9. └─services
  10. └─webapp
  11. └─WEB-INF
  12. └─test
  13. └─java
  14. ├─spiB
  15. └─src
  16. ├─main
  17. ├─java
  18. └─com
  19. └─zs
  20. ├─resources
  21. └─META-INF
  22. └─services
  23. └─webapp
  24. └─WEB-INF
  25. └─test
  26. └─java
  27. ├─spiDemo
  28. └─src
  29. ├─main
  30. ├─java
  31. └─com
  32. └─zs
  33. ├─resources
  34. └─webapp
  35. └─WEB-INF
  36. └─test
  37. └─java
  38. └─spiInterface
  39. └─src
  40. ├─main
  41. ├─java
  42. └─com
  43. └─zs
  44. ├─resources
  45. └─webapp
  46. └─WEB-INF
  47. └─test
  48. └─java
  49. └─spiInterface

spiDemo


  1. package com.zs;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. import java.sql.Statement;
  7. import java.util.Iterator;
  8. import java.util.ServiceLoader;
  9. public class Launcher {
  10. public static void main(String[] args) throws Exception {
  11. // jdbcTest();
  12. showSpiPlugins();
  13. }
  14. private static void jdbcTest() throws SQLException {
  15. String url = "jdbc:mysql://localhost:3306/test";
  16. Connection conn = DriverManager.getConnection(url, "root", "root");
  17. Statement statement = conn.createStatement();
  18. ResultSet set = statement.executeQuery("select * from test.user");
  19. while (set.next()) {
  20. System.out.println(set.getLong("id"));
  21. System.out.println(set.getString("userName"));
  22. System.out.println(set.getInt("age"));
  23. }
  24. }
  25. private static void showSpiPlugins() {
  26. ServiceLoader<IOperation> operations = ServiceLoader.load(IOperation.class);
  27. Iterator<IOperation> operationIterator = operations.iterator();
  28. while (operationIterator.hasNext()) {
  29. IOperation operation = operationIterator.next();
  30. System.out.println(operation.operation(6, 3));
  31. }
  32. }
  33. }

SPI示例 完整代码。
原文链接https://blog.csdn.net/lemon89/article/details/79189475
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

组件化框架设计之Java SPI机制(三)的更多相关文章

  1. 组件化框架设计之AOP&IOC(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将从以下两个方面来介绍组件化框架设计: [AOP(面向切 ...

  2. 组件化框架设计之apt编译时期自动生成代码&动态类加载(二)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将继续从以下两个内容来介绍组件化框架设计: apt编译时 ...

  3. Android组件化框架设计与实践

    在目前移动互联网时代,每个 APP 就是流量入口,与过去 PC Web 浏览器时代不同的是,APP 的体验与迭代速度影响着用户的粘性,这同时也对从事移动开发人员提出更高要求,进而移动端框架也层出不穷. ...

  4. 组件化框架设计之阿里巴巴开源路由框架——ARouter原理分析(一)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 背景 当项目的业务越来越复杂,业务线越来越多的时候,就需要按照业 ...

  5. JDK源码解析之Java SPI机制

    1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...

  6. [Android Pro] 终极组件化框架项目方案详解

    cp from : https://blog.csdn.net/pochenpiji159/article/details/78660844 前言 本文所讲的组件化案例是基于自己开源的组件化框架项目g ...

  7. Android组件化框架项目详解

    简介 什么是组件化? 项目发展到一定阶段时,随着需求的增加以及频繁地变更,项目会越来越大,代码变得越来越臃肿,耦合会越来越多,开发效率也会降低,这个时候我们就需要对旧项目进行重构即模块的拆分,官方的说 ...

  8. Atlas-手淘组件化框架的前世今生和未来的路

    今天手淘技术团队宣布正式开源它们的容器框架Atlas,项目地址: https://github.com/alibaba/atlas 同时他们还推出了项目官网,上线了技术文档: http://atlas ...

  9. Java spi机制浅谈

    最近看到公司的一些框架和之前看到的开源的一些框架的一些服务发现和接入都采用了java的spi机制. 所以简单的总结下java spi机制的思想. 我们系统里抽象的各个模块,往往有很多不同的实现方案,比 ...

随机推荐

  1. go web编程——路由与http服务

    本文主要讲解go语言web编程中的路由与http服务基本原理. 首先,使用go语言启动一个最简单的http服务: package main import ( "log" " ...

  2. JavaScript给动态插入的元素添加事件绑定

    由于实际的需要,有时需要往网页中动态的插入HTML内容,并在插入的节点中绑定事件处理函数.我们知道,用Javascript向HTML文档中 插入内容,有两种方法, 一种是在写HTML代码写入JS,然后 ...

  3. 源码分析--ArrayList(JDK1.8)

    ArrayList是开发常用的有序集合,底层为动态数组实现.可以插入null,并允许重复. 下面是源码中一些比较重要属性: 1.ArrayList默认大小10. /** * Default initi ...

  4. php中__call与__callstatic()使用

    php 5.3 后新增了 __call 与__callStatic 魔法方法. __call 当要调用的方法不存在或权限不足时,会自动调用__call 方法. __callStatic 当调用的静态方 ...

  5. 只用ipv6 两台机器共享文件夹, 局域网连接路径,共享文件夹路径中ipv6地址如何表示

    1. 首先要确认你的DNS服务器支持IPv6,一般是指网络中的路由. 2. 如果网络中没有路由,则直接在hosts文件中添加对方的IPv6地址与名字. 3. 利用UNC路径,把冒号修改为连字符并附加. ...

  6. 微信小程序(5)--阅读器

    最近用微信小程序写了一个图书阅读器,可以实现左右滑动翻页,按钮翻页,上下滚动,切换背景,控制字体大小.以及记住设置好的状态,如页面再次进来保留上次的背景色和字体大小. 由于暂时没有真实的数据接口,所以 ...

  7. Collection集合家族

    集合家族 数组:存储相同类型的多个元素 对象:存储不同类型的多个元素 集合:存储多个不同类型的对象 List List继承自Collection接口,是有序可重复的集合. 它的实现类有:ArrayLi ...

  8. Git--08 Jenkins

    目录 Jenkins 01. 安装准备 02 .安装Jdk和Jenkins 03 .配置Jenkins 04. 插件安装 05. 创建项目 06. Jenkins获取Git源代码 07. 立即构建获取 ...

  9. hadoop_hdfs_上传文件报错

    错误提示: INFO hdfs.DFSClient: Exception in createBlockOutputStream java.io.IOException: Bad connect ack ...

  10. Matlab中利用null函数解齐次线性方程组

    摘自:http://blog.csdn.net/masibuaa/article/details/8119032 有齐次线性方程AX=0,且rank(A)=r<n时,该方程有无穷多个解, 可以用 ...