Java SPI详解

1. SPI概述

1.1 什么是SPI

SPI全称Service Provider Interface,是一种服务发现机制。为了被第三方实现或扩展的 API,它可以用于实现框架扩展或组件替换。

SPI 机制本质是将 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载文件中的实现类,这样运行时可以动态的为接口替换实现类。

SPI本质上就是动态指定接口的实现类,比如FileStoreService有3个实现类,具体要使用哪一个实现类可以通过SPI动态的获取某一个实现类。

SPI实现了接口与实现类的解耦,提高了应用的可扩展性。

具体步骤就是,首先创建一个接口与多个实现类,然后再META-INF/services目录下创建接口的权限定名称文件,配置内容是接口实现类的权限定名称,这样就可以通过Service.load获取所有的实现类,根据配置获取指定实现类。

1.2 适用场景

SPI 最合适的还是没有统一业务实现场景,就像通过sharding-jdbc的数据加密算法就是通过SPI方式扩展的,其中还有dubbo的SPI等等。

2. SPI原理

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) {
                //..
            }
            //..
        }
    }
}

3.SPI案例

3.1 项目需求

这里举的例子是存储服务,实现可以支持不同云服务的存储,当然需求可以有很多实现方式,策略设计模式亦可以,SPI也没问题,只是为了说明问题。
为了提高可扩展性,支持不同云存储,采用SPI动态加载不同服务实现类。

3.2 项目结构

在这里插入图片描述

3.3 项目地址

gitee地址:https://gitee.com/rjzhu/spi/tree/master

application.properties

file.store.type=OSS

路径:META-INF.services
文件名:com.hello.spi.service.FileStoreService

com.hello.spi.service.impl.COSFileStoreServiceImpl
com.hello.spi.service.impl.OBSFileStoreServiceImpl
com.hello.spi.service.impl.OSSFileStoreServiceImpl

Constants

package com.hello.spi.common;

/**
 * @author zrj
 * @since 2021/6/28
 **/
public class Constants {
    /**
     * 阿里云OSS
     */
    public static final String OSS_FILE_STORE = "OSS";

    /**
     * 腾讯云COS
     */
    public static final String COS_FILE_STORE = "COS";

    /**
     * 华为云OBS
     */
    public static final String OBS_FILE_STORE = "OBS";

}

FileStoreController

package com.hello.spi.controller;

import com.hello.spi.factory.FileServiceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
 * SPI测试控制器
 *
 * @author zrj
 * @since 2021/6/28
 **/
@RestController
@RequestMapping("/file")
public class FileStoreController {

    @Autowired
    private FileServiceFactory fileServiceFactory;

    /**
     * SPI测试,文件下载
     */
    @GetMapping("download")
    public String fileDownload() {
        fileServiceFactory.download("");

        return "hello SPI download";
    }

    /**
     * SPI测试,文件上传
     */
    @GetMapping("upload")
    public String fileUpload() {
        InputStream inputStream = new ByteArrayInputStream("".getBytes());
        fileServiceFactory.upload(inputStream);

        return "hello SPI upload";
    }
}

FileServiceFactory

package com.hello.spi.factory;

import com.hello.spi.service.FileStoreService;
import lombok.var;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;

/**
 * @author zrj
 * @since 2021/6/28
 **/
@Component
public class FileServiceFactory implements InitializingBean {

    @Value("${file.store.type:OSS}")
    private String fileStoreType;

    private FileStoreService fileStoreService;

    private Map<String, FileStoreService> fileStoreServiceMap = new HashMap<>();

    public Object upload(InputStream inputStream) {
        return fileStoreService.upload(inputStream);
    }

    public Object download(String fileId) {
        return fileStoreService.download(fileId);
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        var serviceLoader = ServiceLoader.load(FileStoreService.class);
        var fileStoreServiceIterator = serviceLoader.iterator();
        while (fileStoreServiceIterator.hasNext()) {
            var customerFileStoreService = fileStoreServiceIterator.next();
            fileStoreServiceMap.put(customerFileStoreService.getType(), customerFileStoreService);
        }

        fileStoreService = fileStoreServiceMap.get(fileStoreType);
    }
}

FileStoreService

package com.hello.spi.service;

import java.io.InputStream;

/**
 * 存储服务接口
 *
 * @author zrj
 * @since 2021/6/28
 **/
public interface FileStoreService {

    /**
     * 文件上传
     *
     * @param inputStream 文件流
     * @return Object 返回对象
     */
    Object upload(InputStream inputStream);


    /**
     * 文件上传
     *
     * @param fileId 文件ID
     * @return Object 返回对象
     */
    Object download(String fileId);

    /**
     * 存储类型
     */
    String getType();
}

3个实现类都是空方法,只是为了验证SPI,就不在列举了,具体建gitee。

参考:如何优雅使用 SPI 机制

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值