您好,欢迎来到爱go旅游网。
搜索
您的当前位置:首页JDK SPI 机制,底层探究。

JDK SPI 机制,底层探究。

来源:爱go旅游网

JDK SPI 机制

什么是 SPI

​ SPI(Service Provider Interface)主要是被框架开发人员使用的一种技术。例如,使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现

SPI 怎么用

Log 接口

public interface log {
    void info(String msg);
    void error(String msg);
}

接口实现类

log4jImpl

@Slf4j
public class log4jImpl implements log {
    @Override
    public void info(String msg) {
        log.info("log4j: {}", msg);
    }

    @Override
    public void error(String msg) {
        log.error("log4j: {}", msg);
    }
}

lombackImpl

@Slf4j
public class lombackImpl implements log {
    @Override
    public void info(String msg) {
        log.info("lomback: {}", msg);
    }

    @Override
    public void error(String msg) {
        log.error("lomback : {}", msg);
    }
}

建立配置文件

spi.log4jImpl
spi.lombackImpl

测试

 */
public class logTest {
    public static void main(String[] args) {
        ServiceLoader<log> serviceLoader = ServiceLoader.load(log.class);

        Iterator<log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()){
            log log = iterator.next();
            log.info("java spi");
        }
    }
}

测试结果

Connected to the target VM, address: '127.0.0.1:746', transport: 'socket'
 INFO - log4j: java spi
 INFO - lomback: java spi
Disconnected from the target VM, address: '127.0.0.1:746', transport: 'socket'

SPI 底层实现

安装测试程序的结果,开始进行探究。首先抛出问题。

  • 为什么是加载的 META-INF/services/ 这个目录
  • 为什么在 文件中填写全类名之后,便可拿到类的实现

ServiceLoader.load(log.class)

在这里方法中,我们会的到当前的类加载器(AppClassLoader), 随后调用 load 方法创建了一个 ServiceLoader 。

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

ServiceLoader

ServiceLoader 实现了迭代器

  • PREFIX
    • 我们编写配置文件的路径
  • service 我们传递的接口
  • loader 类加载器
  • providers 一个链表
public final class ServiceLoader<S> implements Iterable<S>
{   
	private static final String PREFIX = "META-INF/services/";
    // The class or interface representing the service being loaded
    private final Class<S> service;
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
}

ServiceLoader 的next() 、 hasNext() 方法

直观看到其调用了 nextService(); hasNextService();

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

nextService(); hasNextService();

hasNextService() 方法用来解析文件,将文件的全类名进行校验之后,放入一个迭代器中。

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 拼接成我们编写实现类文件的全路径
                    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);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // 解析文件, 一行一行解析,并且 pending 也是一个迭代器。
                pending = parse(service, configs.nextElement());
            }
            // 从迭代器中拿一个元素。
            nextName = pending.next();
            return true;
        }

nextService()

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            // hasNextService() 方法执行完毕之后,会更新 nextName 字段。
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 按照全类名来加载这个类
                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 {
                //  c.newInstance() 使用Class 创建这个类的实例
                // service.cast() 将类实例(log接口的实现类)转化为 log 接口 
                S p = service.cast(c.newInstance());
                // 放入链表中,在hasNextService解析文件的时候,会判断providers中是否包含待解析的 全类名。
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

SPI 有什么用

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- igat.cn 版权所有 赣ICP备2024042791号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务