ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【概念简析】浅谈Java SPI机制的理解及应用

2022-02-10 21:00:08  阅读:204  来源: 互联网

标签:Java 浅谈 service 接口 public SPI 简析 ServiceLoader 加载


Java SPI(Service Provider Interface),是JDK提供的一套用来被第三方实现或者扩展的接口,通过java.util.ServiceLoader类加载META-INF/services/中的配置进行服务发现,可以用来启用框架扩展和替换组件。主要好处在于解耦,可拔插,面向接口编程,本质是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制。

这种思想被广泛的应用到各种框架及其实现中,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和Oracle都有不同的实现提供,而Java的SPI机制可以为某个接口寻找服务实现。再比如Spring中也有一种类似与Java SPI的扩展加载机制,在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。

这里要注意SPI与API区别:

  • API大多数情况下,都是实现方制定并、实现接口,调用方仅仅调用接口,且不能选择实现, 从使用人员上来说,API 一般被应用开发人员使用;
    image

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

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

使用Java的SPI来模拟一个简单的实现
定义一个数据源加载接口

/**
 * 数据源驱动程序
 *
 * @author starsray
 * @since 2022-02-10
 */
public interface DatasourceDriver {
    void loadDriver();
}

根据规范实现接口

  • MySQL实现
/**
 * mysql driver
 *
 * @author starsray
 * @since 2022-02-10
 */
public class MySQLDriver implements DatasourceDriver {
    @Override
    public void loadDriver() {
        System.out.println("loaded mysql driver");
    }
}
  • Oracle实现
/**
 * oracle driver
 *
 * @author starsray
 * @since 2022-02-10
 */
public class OracleDriver implements DatasourceDriver {
    @Override
    public void loadDriver() {
        System.out.println("loaded oracle driver");
    }
}

测试代码

/**
 * TestSPI
 *
 * @author starsray
 * @since 2022-02-10
 */
public class TestSPI {

    public static void main(String[] args) {
        ServiceLoader<DatasourceDriver> drivers = ServiceLoader.load(DatasourceDriver.class);
        for (DatasourceDriver d : drivers) {
            d.loadDriver();
        }
    }
}

输出

loaded mysql driver
loaded oracle driver

ServiceLoader简析,查看JDK1.8的源码
image

主要成员变量

// 默认扫描路径
private static final String PREFIX = "META-INF/services/";

// 被加载的类或者接口
private final Class<S> service;

// 用于定位、加载和实例化实现方实现的类的类加载器
private final ClassLoader loader;

// ServiceLoader被创建后的上下文对象
private final AccessControlContext acc;

// 按照实例化的顺序缓存已经实例化的类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// The current lazy-lookup iterator 懒加载器
private LazyIterator lookupIterator;

当调用静态load方法时,会创建一个新的ServiceLoader对象。

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

私有化构造方法初始化,做加载类判空,如果没有自定义类加载器,使用系统类加载器,并且初始化上下文对象,调用reload方法。

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

reload方法中,清理服务提供缓存,创建懒加载迭代器。此时并不会加载服务对象。

public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

由于LazyIterator实现了Iterator接口,当通过迭代器循环时,扫描获取所有的服务类并装载到缓存中,供下次使用。

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
        }

上面简单分析了Java SPI的实现ServiceLoader,借助于这种思想,每种框架的实现方式大同小异,比如Spring Boot Starter还引入了注解扫描,注册,按需加载的实现形式。

完整代码示例:https://gitee.com/starsray/test/tree/master/spi-test

标签:Java,浅谈,service,接口,public,SPI,简析,ServiceLoader,加载
来源: https://www.cnblogs.com/starsray/p/15595415.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有