SPI实例与简单源码分析

SPI全称Service Provider Interface,见名知意,是提供给服务供应商的一个接口,是解耦思想的一个体现,它通过将服务进行接口定义,提前对接口进行一个基础功能模板的编程的形式,充分运用了模板方法的设计思想,让每个开发商根据自己的实际实现对应的接口方法,更加灵活,使用起来也更加方便。

实际操作

开发一个SPI接口需要两步,分别为服务使用端接口定义和服务供应商具体实现,像JDBC的具体实现就是如此,如果要增加可插拔性,也可以将接口独立出来,形成一个项目,让主项目去依赖,下面做一个最基础的实现。

服务使用端接口定义

想象一下我们需要做一个万能遥控器,能够操作对应跑步机的跑步功能,但是具体哪个开发商要来使用我们这个遥控器不得而知
1.创建一个Maven项目
2.开发接口类
2.1 目录结构
在这里插入图片描述
2.2 service接口

package com.spi;

public interface ServiceInterface {
  public void run();
}

2.3 使用服务

package com.service;
import com.spi.ServiceInterface;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Controller {
  public void start(){
  //通过ServiceLoader去找对应META-INF/services目录下的文件,通过文件能够找到接口在jar中的具体实现位置,然后反射进行一个实例化
      ServiceLoader<ServiceInterface> serviceInterfaces = ServiceLoader.load(ServiceInterface.class);
      Iterator<ServiceInterface> iterator = serviceInterfaces.iterator();
      while (iterator.hasNext()){
          ServiceInterface next = iterator.next();
          next.run();
      }
  }
}

2.4 测试类

package com;
import com.service.Controller;
import org.junit.Test;
public class TestController {
    @Test
    public void test(){
        Controller controller = new Controller();
        controller.start();
    }
}

3.安装到本地maven仓库
当然此时还不能正确允许测试类因为没有具体实现

服务使用端接口定义好了之后,用maven install指令安装到本地仓库,以方便服务供应商引入

服务供应商具体实现

1.创建一个Maven项目,并引入刚刚安装的接口

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
	<!-- 为了引入接口 -->
    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spi-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <!-- 加入插件防止maven进行build的时候把META-INF目录下面的文件进行一个覆盖,因为我们的目的是要往META-INF下写入实现类的信息 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.具体实现类
2.1 目录结构
用idea用习惯的同学注意一下META-INF/services这个目录创建的时候分两步创建,不要用点去分割,build的时候会误认为这是一个名叫“META-INF.services”的目录,导致最后ServiceLoader找不到对应的路径
在这里插入图片描述
2.2 实现类

package com;
import com.spi.ServiceInterface;

public class Provider1 implements ServiceInterface {
    public void run() {
        System.out.println("Provider1 running machine running !!!! ");
    }
}

2.3 service下面创建一个以接口名为名字的文件,内容是接口的具体实现
在这里插入图片描述
2.4 maven install当前类

测试

1.准备工作基本完成,但是还需要将两者联系起来,回到服务使用端,将刚刚安装的实现类使用maven引入
在这里插入图片描述
2.运行测试类,发现使用到了供应商提供的类实例
在这里插入图片描述

为什么会去读取META-INF/services下面的文件

带着这个疑问,翻阅了下ServiceLoader的源码(jdk1.8),发现默认ServiceLoader采用一种懒加载的形式,在load的时候实际并没有将类加载到内存,只是以当前类加载器和类的class对象创建了一个ServiceLoader实例而已,真正读取数据是在ServiceLoader实现的迭代器方法中,具体过程如下

1.这个方法是实际加载类的方法,最终会回调nextService方法

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

2.第一行是关键,先去找是否有服务供应商提供具体服务,也就是去找到对应具体类全名,所以下一步查看hasNextService方法

        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
        }

3.如下,hasNextService的具体代码,分析写在代码中了

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                //这里就是去拼写出要找的类路径,PREFIX定义如下
                //PREFIX = "META-INF/services/"
                //service.getName()即为类全名,所以会去找
                //META-INF/services/com.spi.ServiceInterface的文件
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                    //这边使用ClassLoader.getSystemResources打破双亲委派机制
                        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进行赋值,为之后实例化做准备
            nextName = pending.next();
            return true;
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值