设计原则:API 和 SPI

API
全称:Application Programming Interface 应用程序接口。

SPI
全称:Service Provider Interface,是一种服务发现机制。

面向接口编程:
在这里插入图片描述
上图中,我们在“调用方”和“实现方”之间引入了“接口”,但是并没有给出“接口”应该位于哪个位置,单从可能性考虑,有三种情况:
1、“接口”位于“调用方”所在的包中
2、“接口”位于“实现方”所在的包中
3、“接口”位于独立的“包”中。

接下来,我们针对这三种场景进行详细介绍
场景一:接口位于调用方所在的包中
对于类似这种情况下接口,我们将其称为“SPI”,全程为:service provider interface,

SPI的规则如下
概念上更依赖调用方。
组织上位于调用方所在的包中。
实现位于独立的包中。
常见的例子是:插件模式的插件。

SPI 最常用的场景就是插件的开发。比如eclipse制定接口标准,然后开发人员根据这些接口标准去做具体实现,来开发插件。

SPI 开发步骤
2.1 服务方提供SPI接口
2.2 提供方(或者调用方)实现接口
2.3 服务方利用服务发现机制,找到SPI接口的实现。

使用场景
概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

比较常见的例子:
数据库驱动加载接口实现类的加载:JDBC加载不同类型数据库的驱动
日志门面接口实现类加载:SLF4J加载不同提供商的日志实现类
Spring:Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
Dubbo:Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口

场景二:接口位于实现方所在的包中
我们先想象一个场景,以Unity提供的IUnityContainer接口为例,除了维护这个框架的团队之外,我们没有发现谁实现了这个接口,虽然理论上是可以实现这个接口的(如果能实现的话,我们何不自己弄额Ioc容器呢?)。

对于类似这种情况下的接口,我们将其称作为“API”,“API”的规则如下:

概念上更接近实现方。
组织上位于实现方所在的包中。
实现和接口在一个包中。

场景三:接口位于独立的包中
这里就不说场景了。

如果一个“接口”在一个上下文是“API”,在另一个上下文是“SPI”,那么你就可以这么组织。

spi的使用分为两种,可以使用java内置的spi,也可以使用spring封装的spi。

例子:使用Java内置spi的步骤
1.创建接口和他的多个实现类

package org.spring.springboot.service;

public interface Animal {

    void eat();
}
package org.spring.springboot.service.impl;

import org.spring.springboot.service.Animal;

public class Cat implements Animal {

    @Override
    public void eat() {
        System.out.println("喵喵喵... ...");
    }
}
package org.spring.springboot.service.impl;

import org.spring.springboot.service.Animal;

public class Dog implements Animal {

    @Override
    public void eat() {
        System.out.println("汪汪汪... ...");
    }
}

2.在项目的Resource/META-INF/Services目录下创建一个和接口全限定名一样的文件,在里面写入接口的实现类的全限类名
在这里插入图片描述
3.使用serviceloader加载

public static void main(String[] args) {
        Animal animal = null;
        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);//查找spi的实现
        Iterator<Animal> searchs = serviceLoader.iterator();
        while (searchs.hasNext()) {
            animal = searchs.next();
            System.out.println(animal);
        }
    }

在这里插入图片描述
可以看到会加载配置文件中写的实现类,并且会加载所有的实现类。

例子:使用Spring spi的步骤
Spring SPI是对java spi进行了封装增强,使用SpringFactories的loadFactories方法可以返回得到配置文件中写的实现类的集合,并且可以选择需要的某个实现类。

Spring中SPI扩展机制原理
在Springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个jar包中搜索所有META-INF/spring.factories
配置文件,其实这里不仅仅是去ClassPath路径下查询,而是会扫描所有路径下的jar包,只不过这个文件只会在ClassPath下的jar包中.

首先要添加需要的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.1.6.RELEASE</version>
    <optional>true</optional>
</dependency>
public static void main(String[] args) {
        //获取spi的所有实现
        List<Animal> animals = SpringFactoriesLoader.loadFactories(Animal.class, null);
        Animal animal = animals.get(1);
        animal.eat();
    }

查看源码可以发现,对配置文件位置也有要求
在这里插入图片描述
对比不难发现,SpringSPI使用起来更方便,实现类的实例都存放在List集合中,调用方可以任意选择。

在项目中,只需要修改调用方的spring.factories文件和引入实现类的jar包,就可以快速选择适合的实现了。

参考资料:
https://blog.csdn.net/Kirito_j/article/details/106446288?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.fixedcolumn&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.fixedcolumn

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值