简介

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦

SPI整体机制图如下:

java基础:SPI机制

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader

基本使用

创建如下结构的maven项目:

java基础:SPI机制

其中app子项目maven:

    <dependencies>
        <dependency>
            <groupId>com.wj</groupId>
            <artifactId>interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.wj</groupId>
            <artifactId>mysql-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.wj</groupId>
            <artifactId>redis-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

我在interface项目中声明了一个接口com.wj.ISaveService

public interface ISaveService {
    void save();
}

mysql-impl项目和redis-impl项目对ISaveService接口实现如下:

public class MysqlSaveService implements ISaveService {
    @Override
    public void save() {
        System.out.println("mysql save...");
    }
}
public class RedisSaveService implements ISaveService {
    @Override
    public void save() {
        System.out.println("redis save...");
    }
}

mysql-impl的resource目录下新建META-INF/services/包,创建名称为com.wj.ISaveService的文件与interface中接口全类名相同,文件内容如下(为该接口的实现类的全类名相同):

com.wj.impl.MysqlSaveService

redis-impl与mysql-impl类似,文件内容如下:

com.wj.impl.RedisSaveService

app项目的main方法:

public class Main {

    public static void main(String[] args) {
        //使用ServiceLoader加载所有的ISaveService接口
        ServiceLoader<ISaveService> serviceLoader = ServiceLoader.load(ISaveService.class);

        for (ISaveService iSaveService : serviceLoader) {
            iSaveService.save();
        }
    }
}

运行结果:

java基础:SPI机制

应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。

比如JDBC场景下:

  • 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。

在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。

同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

原理

如果想要打断点调试,可以修改一下main方法:

        ServiceLoader<ISaveService> serviceLoader = ServiceLoader.load(ISaveService.class);
        Iterator<ISaveService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            ISaveService next = iterator.next();
            next.save();
        }

源码不算复杂,当调用iterator的hasNext方法时,内部使用的LazyIterator会去找到对应的文件。

可以看:

private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //private static final String PREFIX = "META-INF/services/";
                    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;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //获取Class
                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
        }

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

        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 void remove() {
            throw new UnsupportedOperationException();
        }

    }

缺点

1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。

参考:https://zhuanlan.zhihu.com/p/84337883

相关文章:

  • 2021-06-30
  • 2022-12-23
  • 2021-04-23
  • 2021-06-15
猜你喜欢
  • 2021-09-24
  • 2021-06-06
  • 2021-11-03
  • 2021-09-11
  • 2021-11-17
相关资源
相似解决方案