JAVA-SPI让框架更优雅

JAVA-SPI让框架更优雅

架构师是做什么的:

  1. 负责公司软件系统的技术路线、架构设计、研发工作;
  2. 承担从产品需求像技术实现转换的桥梁作用,根据产品规划更新技术架构的研发方向;
  3. 参与项目计划评审;
  4. 参与需求分析、建模、软件设计评审;
  5. 负责组织技术研究和攻坚工作;
  6. 负责组织及带领公司内部员工研究与项目相关的新技术;
  7. 管理技术架构团队并给项目、产品开发实施团队提供技术支持;
  8. 理解项目的业务需求,给出软件系统整体解决方案;
  9. 对技术基础架构的相关技术和业务进行培训,指导开发人员开发。

What?

**SPI(Service Provider Interface)机制:**一种服务发现机制,其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了SPI接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔.

使用场景:不关心用户的输入参数类型,用户只关心相互输入后得到的结果 eg:JDBC、JNDI、Dubbo、SpringBoot都使用了SPI技术

架构师的一剂灵丹妙药

利用配置文件实现热插拔 eg:SpringBoot(META-INF/spring.factories)

eg:解析歌曲的项目(SPI应用)

  • **song-parser 项目。**负责定义通用的歌曲解析接口,并不提供任何具体的歌曲解析器实现。
  • **song-parser-XXX(mp3) 项目。**实现了 song-parser 项目的歌曲解析接口,实现了 mp3 格式歌曲的解析。
  • **song-parser-XXX(mp4) 项目。**实现了 song-parser 项目的歌曲解析接口,实现了 mp4 格式歌曲的解析。

SPI与API的不同应对场景

APIApplication Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

SPIService Provider Interface)调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

SPI机制

  1. 创建指定的文件夹【/resources/META-INF/services】并创建于接口的全限定名一致的文件,SPI内部加载实现类的时候指定了该扫描目录
    在这里插入图片描述

  2. 可以实现扩展的热插拔,需要添加扩展只需要扩展实现该接口,打包成.mvn依赖添加即可

  3. JDBC,DUBBO等实现都是基于spi机制实现的。

SPI原理 关键实现ServiceLoader

源码了解:
public final class ServiceLoader<S> implements Iterable<S> {
    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";
    // 被加载的类或接口
    private final Class<S> service;
    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;
    // 上下文对象
    private final AccessControlContext acc;
    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;

    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;
        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取目录下所有的类 转换为-URL,工作流的方式获取每一行的数据并截取第一个‘#’之前的数据,并去除前后的空格
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类 将上面获取的url进行转换为对象类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

SPI使用

要使用SPI比较简单,只需要按照以下几个步骤操作即可:

  • 在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名
  • 接口实现类所在的jar包在classpath下
  • 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM
  • SPI的实现类必须带一个无参构造方法

Demo

eud-spi-play:定义接口

pom.xml
package org.dh.spi;
public interface SpiPlayService {
    String play();
}

edu-spi-mp3play:接口的实现

pom.xml
<dependency>
    <groupId>org.dh</groupId>
    <artifactId>eud-spi-play</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
org.dh.spi.SpiPlayService:
	org.dh.spi.impl.Mp3PlayServiceImpl
java:
public class Mp3PlayServiceImpl implements SpiPlayService {
    @Override
    public String play() {
        return "mp3-play";
    }
}

edu-spi-mp4play:接口的实现

pom.xml
<dependency>
    <groupId>org.dh</groupId>
    <artifactId>eud-spi-play</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
org.dh.spi.SpiPlayService:
	org.dh.spi.impl.Mp4PlayServiceImpl
java:
public class Mp4PlayServiceImpl implements SpiPlayService {
    @Override
    public String play() {
        return "mp4-play";
    }
}

**edu-spi-demo:**demo

pom.xml
<dependency>
    <groupId>org.dh</groupId>
    <artifactId>edu-spi-mp3play</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.dh</groupId>
    <artifactId>edu-spi-mp4play</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
java:
public class SpiTest {
    @Test
    public void testSpi(){
        ServiceLoader<SpiPlayService> serviceLoader = ServiceLoader.load(SpiPlayService.class);
        for(SpiPlayService spiPlayService:serviceLoader){
            System.out.println("paly:"+spiPlayService.play());
        }
    }
}
sout.out:
paly:mp3-play
paly:mp4-play

总结:

关于spi的详解到此就结束了,总结下spi能带来的好处:

  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值