SPI源码解析

SPI源码解析

1. 什么是SPI

SPI是Service Provide Interface的简称,是JDK内置的一种服务提供发现机制。简单来说, 就是用来发现某个接口或者规范的实现类。

2. 快速开始

2.1 项目结构

在这里插入图片描述

2.2 类图

2.3 相关代码
  • Service接口类
package com.inconspicuousy.spi;

/**
 * 待实现的接口类
 * @author peng.yi
 */
public interface Service {

    void say();

}
  • ServiceImpl接口实现类
package com.inconspicuousy.spi.impl;

import com.inconspicuousy.spi.Service;

/**
 * Service的实现类
 * @author peng.yi
 */
public class ServiceImpl implements Service {
    public void say() {
        System.out.println("我实现了Service的say方法.");
    }
}
  • ServiceFactory简单工厂类
package com.inconspicuousy.spi.facroty;

import com.inconspicuousy.spi.Service;

import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;

/**
 *
 * 创建Service的工厂类
 * @author peng.yi
 */
public class ServiceFactory {

    public static Service getService() {
        ServiceLoader<Service> load = ServiceLoader.load(Service.class);
        // ServiceLoader 继承 Iterable, 调用spliterator方法结合StreamSupport.stream返回Stream
        // 最后在 MATE-INF.service下面找到对应的实现类集合
        // 返回任意一个实现类
        Optional<Service> service = StreamSupport.stream(load.spliterator(), false).findAny();
        return service.orElse(null);
    }
}

这里SPI技术的核心就是利用ServiceLoad类加载接口类时,会主动到对应的目录文件中找到对应的实现类并完成加载。

  • META-INF/services/com.inconspicuousy.spi.Service
com.inconspicuousy.spi.impl.ServiceImpl

指定文件中填写接口的实现类的包名+类名,然后类加载器加载指定的实现类。

  • ServiceImplTest测试类
package com.inconspicuousy.spi.impl;

import com.inconspicuousy.spi.Service;
import com.inconspicuousy.spi.facroty.ServiceFactory;
import org.junit.Test;

/**
 * @author peng.yi
 */
public class ServiceImplTest {

    @Test
    public void test() {

        Service service = ServiceFactory.getService();
        System.out.println(service);
        if (service != null) {
            service.say();
        }
    }
}
2.4 直接测试结果
com.inconspicuousy.spi.impl.ServiceImpl@606d8acf
我实现了Service的say方法.

Process finished with exit code 0

3. SPI的核心实现逻辑

  • ServiceLoader继承Iterable接口重写了iterator方法
 public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                // 核心逻辑就是调用 lookupIterator.hasNext()
                // lookupIterator是ServiceLoader内部类LazyIterator的对象
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }
  • lookupIterator.hasNext()方法里面本质上调用的就是LazyIterator#hasNextService
  • lookupIterator.next()方法里面本质上调用的就是LazyIterator#nextService
        public boolean hasNext() {
            // 这里不管 acc是否为空 都是调用的 hasNextService方法
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
             // 这里不管 acc是否为空 都是调用的 nextService 方法
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
  • hasNextService方法中解析META-INF/services/接口全类名文件, 并将里面的内容加载到String集合中
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // private static final String PREFIX = "META-INF/services/";
                    // service => 接口全类名
                    String fullName = PREFIX + service.getName();
                    // 利用类加载器加载该文件
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            // 非首次加载的话, 直接判断pending迭代器的是否拥有下一个元素
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // Iterator<String> pending = null;
                // 这里首次将文件中的内容加载到String类型的集合中
                pending = parse(service, configs.nextElement());
            }
            // 将实现类的全类名赋值给nextName属性
            nextName = pending.next();
            return true;
        }
  • nextService方法会将实现类加载到JVM中
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 根据实现类的全类名,将实现类加载到JVM中
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                // 创建实例对象, 并将对象存储到 providers 集合中并将实例返回
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
3.1 实现总结

本质上就是读取META-INF/services下的文件, 然后实现类的全类名并将其加载到JVM中创建对象并返回。

4. SPI的应用

4.1 数据库驱动

JDK中只是定义了Driver的规范,但是没有具体的实现。 在项目中,一般我们根据数据库的不同(Mysql、Oracle)引入不同的数据库驱动(Driver的实现类)就可以获取到数据库连接对数据库进行操作。

4.1.1 源码分析
4.1.1.1 前置准备
  • 引入mysql-connector-java依赖
 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <!-- 引入Mysql连接包, mysql连接包中就定义了数据库驱动的Driver的实现类的信息 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
  • 编写DriverTest测试类
package com.inconspicuousy.spi.impl;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author peng.yi
 */
public class DriverTest {

    @Test
    public void test() throws SQLException {

        // 通过DriverManager获取数据库连接
        Connection root = DriverManager.getConnection("jdbc:mysql://localhost:3306", "root", "xxx");
        System.out.println(root);
    }
}
4.1.1.2 分析
  • 查看mysql-connector-java的jar包, 不难发现在jar包中包含META-INF/services/java.sql.Driver文件, 文件中描述的正是Driver的mysql实现类的全路径com.mysql.cj.jdbc.Driver
  • 在DriverManager中随着类加载的还有静态方法块,其中执行了loadInitialDrivers()
 /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
  • loadInitialDrivers()中有一个核心逻辑就是利用SPI技术加载对应的Driver实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
    while(driversIterator.hasNext()) {
        // 这里就会自动加载mysql的驱动
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}
return null;
  • 在加载com.mysql.cj.jdbc.DriverMysql驱动类时, 随着类加载会执行DriverManager.registerDriver()方法, 并且会创建驱动对象作为参数传递。
 static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
  • DriverManager.registerDriver核心逻辑就是将数据库驱动对象保存到java.sql.DriverManager#registeredDrivers集合中
 public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            // registeredDrivers = new CopyOnWriteArrayList<>();
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }
  • 在执行getConnection方法时,核心就是遍历registeredDrivers集合拿到对应的驱动之后获取数据库连接对象。
 for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    // 获取到driver对象再获取连接, 获取成功后直接返回
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值