SPI 动态服务发现机制

发布时间:2024年01月20日

SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载;

spi可以很灵活的让接口和实现分离, 让API提供者只提供接口, 第三方来实现。

image?

优点:

  • 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

Service 机制分析

第一步:ServiceLoader.load(Class clz);

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

我们看一下 ServiceLoader.load(Driver.class);是如何工作的,注意看第二行代码切换了上下文的 classLoader,一般为 AppClassLoader。

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

ServiceLoader.load(service, cl);接着进入 load 方法

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

看一下 ServiceLoader 的构造方法,这里可能会进行 ClassLoader 的切换,这不太重要,我们看一下 reload();

   private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

reload();方法构造了一个LazyIterator,继续看

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

第二步:利用第一步构造出来的LazyIterator,循环Class.forName(),加载接口实现类

Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
    	while(driversIterator.hasNext()) {
    		driversIterator.next();
    	}
     } catch(Throwable t) {
                // Do nothing
    }

loadedDrivers.iterator();迭代器在迭代时是会用到第一步创建的lookupIterator = new LazyIterator(service, loader);

    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;
                return lookupIterator.hasNext();
            }

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

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

        };
    }

当我们进行driversIterator.next();时,实际上执行的是lookupIterator.hasNext()

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);
            }
 }

当 acc ==null 时,

        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 = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

当我们进行driversIterator.next();时,等同在调用

        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);
            }
        }

当 acc==null 时。最关键的逻辑在这里,Class<?> c = Class.forName(cn, false, loader);,然后S p = service.cast(c.newInstance()); 并放入到providers.put(cn, p);

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            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 {
                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
        }

?

使用场景

JDBC 源码补充

jdk 的 rt 包下Driver.class是个接口,全类型限定为java.sql.Driver?:结构如下:

?image?

第三方mysql-connector-java-8.0.27.jar 下的 Driver 实现了 java.sql.Driver接口。代码如下:

package com.mysql.cj.jdbc;

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

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

第三方mysql-connector-java-8.0.27.jar 的 META-INF/serivces下暴露接口文件,供 SPI 机制发现使用。

?image?

使用方通过 ServiceLoader 发现所有的接口实现类

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

SPI打破双亲委派机制

父加载器委托子加载器加载,打破了双亲委派机制。

  1. 由于 rt 包的类加载是 bootstrap classloader
  2. rt 包中DriverManager在loadInitialDrivers中,使用到了 ServiceLoader 触发 Driver.class的加载
  3. 先加载 java.sql.Driver,此时使用到的是 bootstrap classloader
  4. 然后ClassLoader cl = Thread.currentThread().getContextClassLoader();此时的上下文加载器为 App Classloader。

这种父类加载器委托子类加载的行为边打破了双亲委派机制

参考

https://blog.csdn.net/chen462488588/article/details/123894418

文章来源:https://blog.csdn.net/baijunzhijiang_01/article/details/135708018
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。