Java SPI(Service Provider Interface)

Java SPI是Java标准库提供的一种服务发现机制,它通过在classpath下约定的META-INF/services目录中,定义接口和其实现类之间的对应关系,从而动态加载目标接口的实现类。

通过一个实际例子来具体看一下
1、定义接口

public interface Animal {
    void sayHello();
}

2、实现类
这里搞两个实现类

public class Cat implements Animal {
    @Override
    public void sayHello() {
        System.out.println("喵喵");
    }
}
public class Dog  implements Animal{
    @Override
    public void sayHello() {
        System.out.println("汪汪");
    }
}

3、添加配置文件
在resources目录下创建文件夹META-INF/services。
然后在该目录下创建一个文件,以接口的全包名为文件名
如我们这里的Animal接口就要创建
com.test.Animal文件,然后文件内容为实现类全路径名:
这里是上面两个Dog和Cat实现类

com.test.Cat
com.test.Dog

一般情况下是要将当前工程打包成jar包方式供其它三方进行引用,这里为了简单就只在同一个工程下演示了。

4、接口调用
调用通过JDK提供的工具类ServiceLoader来完成

        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
        serviceLoader.forEach(Animal::sayHello);

这里最后Cat和Dog两个实现类的sayHello方法都会被调到。

5、原理分析
读取就不用说了,就是根据接口全名读取其对应的配置文件。然后加载类是通过Class.forName方式

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

这里看到是调用无参构造函数进行实例化。

使用Java SPI 可以在不引入任何三方框架前提下实现解耦,接口的定义与具体业务实现分离开来。并且在不改变原逻辑情况下,通过修改配置实现类来修改实现。
但是SPI也有一定的缺陷,不能按需加载只能全量扫描加载。例如上面我们配置了两个实现类,一次都会扫描加载。也不够灵活,不能通过一些条件去更精确的加载自己想要的服务实现类。

你可能感兴趣的:(Java,java,开发语言,SPI)