SPI即Service Provider Interface,Service提供者接口:提供给服务提供厂商与扩展框架功能的开发者使用的接口。
java spi提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

很多框架都使用了java的SPI机制,如JDBC4中的java.sql.Driver的SPI实现(mysql驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架;

先来看Java spi如何使用:

  1. 创建接口 A
  2. 创建实现类 A1Impl,A2Impl
  3. 在src/main/resources/ 下建立 /META-INF/services 目录,创建一个文件,文件名为接口 A 的全限定名(包名+类名)
  4. 在文件中指定一个实现类,指定谁就在文件中写上它的全限定名
  5. main方法:
    public static void main(String[] args) {
        ServiceLoader<A> s = ServiceLoader.load(A.class);
        Iterator<A> searchs = s.iterator();
        if(searchs.hasNext()){
            A a= searchs.next();
            a.xxx();
        }
    }

具体实现在ServiceLoader类里。

所以Java的SPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.
如JDBC 数据库驱动包,mysql-connector-java-5.1.45.jar 就有一个 /META-INF/services/java.sql.Driver 里面内容是 com.mysql.jdbc.Driver 。
Java-SPI
Java-SPI

为什么配置文件要放在/META-INF/services/

来分析下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);
    }

这里获得线程上下文的ClassLoader,因为双亲委派模式下上层ClassLoader无法加载下层的类,具体见双亲委派模式破坏-JDBC
初始化了ServiceLoader对象。

public final class ServiceLoader<S>
    implements Iterable<S>
{
    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();
    }
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

例子里iterator()返回的就是LazyIterator,来看看其hasNextnext方法

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

hasNext调用了hasNextService

private static final String PREFIX = "META-INF/services/";

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
//fullName = META-INF/services/java.sql.Driver
                    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;
        }

该方法就是获取META-INF/services/xxx配置文件中你的配置项,也就是实现类的全限定名。这里就是一开始问题的答案。

next方法就是调用Class.forName通过hasNext里得到的nextName 与传过来的ClassLoader加载接口实现类。

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

调用nextService

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);

			......
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);

加载并初始化实现类。

相关文章:

  • 2022-12-23
  • 2021-06-06
  • 2021-11-03
  • 2021-09-11
  • 2021-11-17
  • 2022-02-13
猜你喜欢
  • 2022-12-23
  • 2021-09-08
  • 2021-12-29
  • 2022-02-04
  • 2021-12-05
相关资源
相似解决方案