JDK的SPI机制

SPI的全称是Service Provider Interface, 直译过来就是"服务提供接口",为了降低耦合,实现在模块装配的时候动态指定具体实现类的一种服务发现机制。

概述

参考博客 https://www.cnblogs.com/oskyhg/p/10800051.html

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;

  • 2、接口实现类所在的jar包放在主程序的classpath中;

  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;

  • 4、SPI的实现类必须携带一个不带参数的构造方法;

java SPI应用场景很广泛,在Java底层和一些框架中都很常用,比如java数据驱动加载和Dubbo。Java底层定义加载接口后,由不同的厂商提供驱动加载的实现方式,当我们需要加载不同的数据库的时候,只需要替换数据库对应的驱动加载jar包,就可以进行使用。

SPI实现Demo

git地址:https://gitee.com/pengzhang_master/jdk-spi

项目结构

SPI-api模块提供接口

package com;

public interface SpiDemo {
    void say();
}

SPI-Impl1提供实现类

package com;

public class SpiDemoImpl1 implements SpiDemo {

    public void say() {
        System.out.println("SpiDemoImpl1");
    }
}

在resource下创建MATE-INF.services,创建文件com.SpiDemo,可以看到这个文件名对应SPI-api的接口路径,在该文件下填写本模块的实现类路径com.SpiDemoImpl1

pom文件依赖SPI-api模块

<dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>SPI-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

SPI-Impl2模块和SPI-Impl1模块类似,只是将SpiDemoImpl1改为SpiDemoImpl2

 

SPI-main模块依赖

<dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>SPI-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>SPI-Impl1</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>SPI-Impl2</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

测试

package com;

import java.util.ServiceLoader;

public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<SpiDemo> serviceLoader = ServiceLoader.load(SpiDemo.class);
        for (SpiDemo o : serviceLoader) {
            o.say();
        }
    }
}

SPI源码

从ServiceLoader.load(SpiDemo.class);一路进去会看到有个懒加载迭代器lookupIterator = new LazyIterator(service, loader);

LazyIterator这个迭代器只需要关心hasNext()和next(), hasNext()里面又只是单纯地调用hasNextService().  next()里面单纯地调用了nextService();

private boolean hasNextService() {
     if (nextName != null) {
         // nextName不为空,说明加载过了,而且服务不为空 
         return true;
     }
     // configs就是所有的实现类文件名字
     if (configs == null) {
         try {
             // PREFIX是 /META-INF/services
             // service.getName() 是接口的全限定名称
             String fullName = PREFIX + service.getName();
             // loader == null, 说明是bootstrap类加载器
             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;
             }
             //就是判断一下configs.nextElement()的格式是不是对的
             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 {
         // 尝试一下是否能加载该类
         c = Class.forName(cn, false, loader);
     } catch (ClassNotFoundException x) {
         fail(service,"Provider " + cn + " not found");
     }
     // 是不是service的子类,或者同一个类
     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
 }

所谓的懒加载,就是等到调用hasNext()再查找服务, 调用next()才实例化服务类.

服务提供商安装约定,将具体的实现类名称放到/META-INF/services/xxx下, ServiceLoader就可以根据服务提供者的意愿, 加载不同的实现了, 避免硬编码写死逻辑, 从而达到解耦的目的.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值