java框架基础之SPI机制实现及源码解析

 更新时间:2022年09月20日 09:33:12   作者:nicky_chin  
这篇文章主要为大家介绍了java框架基础之SPI机制实现及源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

1 定义

SPI 的全名为 Service Provider Interface ,用于接口寻找服务实现类

实现方式 >标准制定者制定接口 不同厂商编写针对于该接口的实现类,并在jar的“classpath:META-INF/services/全接口名称”文件中指定相应的实现类全类名 开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能

2 案例实现

比如我们经常看到的缓存类Cache,现在有非常多的缓存框架都会去实现这个接口

标准接口

public interface Cache {
    String getName();
    <T> T get(Object key, Class<T> type);
    void put(Object key, Object value);
    void evict(Object key);
    void clear();
}

厂商的具体接口实现

public class ConcurrentMapCache implements Cache {
    private final String name;
    private final ConcurrentMap<Object, Object> store;
    public ConcurrentMapCache() {
        this("defaultMapCache");
    }
    public ConcurrentMapCache(String name) {
        this(name, new ConcurrentHashMap<>(256), true);
    }
    public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {
        this.name = name;
        this.store = store;
    }
    @Override
    public final String getName() {
        return this.name;
    }
    @Override
    public <T> T get(Object key, Class<T> type) {
        Object value = this.store.get(key);
        if (value != null && type != null && !type.isInstance(value)) {
            throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
        }
        return (T) value;
    }
    @Override
    public void put(Object key, Object value) {
        this.store.putIfAbsent(key, value);
    }
    @Override
    public void evict(Object key) {
        this.store.remove(key);
    }
    @Override
    public void clear() {
        this.store.clear();
    }
}

注意:一定要有默认无参构造器,否则之后无法通过SPI机制实例化对象

配置地址

在resouce下的META-INF\services文件下的spi.Cache文件内容是服务类的全限命名:spi.ConcurrentMapCache

打包jar并引入到项目

测试

public class CacheSpiTest {
    public static void main(String[] args) {
        ServiceLoader<Cache> serviceLoader = ServiceLoader.load(Cache.class);
        Iterator<Cache> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Cache cache = iterator.next();
            System.out.println(cache.getName());
            cache.put("user", "nana");
            System.out.println(cache.get("user", String.class));
        }
    }
}

打印结果:

defaultMapCache
nana

说明获取到了定制接口的实现类对象

通过上述例子,我们知道ServiceLoader是用于通过接口获取接口实现类的工具

3 SPI机制源码分析

3.1 load加载过程

ServiceLoader成员变量

//SPI约定获取扩展接口路径的文件
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 LazyIterator lookupIterator

load()初始化

   public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    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();
    }

load() 方法并没有实例化具体实现类,而是加载需要实例化的对象路径

3.2 实例化过程

 Class<S> service;//通用接口
 ClassLoader loader;//类加载器
Enumeration<URL> configs = null;//厂商接口文件URL的集合
 Iterator<String> pending = null;//接口具体实现的路径类名列表
  public boolean hasNext() {
            if (acc == null) {//访问控制上下文是否为空
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

hasNext() : 先从provider中查找,如果有,返回true;如果没有,通过LazyIterator 来进行查找. 在 hasNext() 方法中会获取当前需要实例化的类名 nextName ,然后在 next() 方法中具体实例化

next(): 先从provider中直接获取,如果有,返回实现类对象实例;如果没有,通过LazyIterator 中 nextService() 来进行获取

 private S nextService() {
            if (!hasNextService()) //获取nextName 需要加载的类名
                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()); //初始化类并类型转换成Cache对象
                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机制实现及源码解析的详细内容,更多关于java框架基础SPI机制的资料请关注脚本之家其它相关文章!

相关文章

  • Java中IO流简介_动力节点Java学院整理

    Java中IO流简介_动力节点Java学院整理

    Java io系统的设计初衷,就是为了实现“文件、控制台、网络设备”这些io设置的通信。接下来通过本文给大家介绍Java中IO流简介,感兴趣的朋友一起看看吧
    2017-05-05
  • POI导出Excel报错No such file or directory的解决方法

    POI导出Excel报错No such file or directory的解决方法

    这篇文章主要为大家详细介绍了POI导出Excel报错No such file or directory的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 详解SpringBoot结合策略模式实战套路

    详解SpringBoot结合策略模式实战套路

    这篇文章主要介绍了详解SpringBoot结合策略模式实战套路,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • java音乐播放器课程设计

    java音乐播放器课程设计

    这篇文章主要为大家详细介绍了java音乐播放器的课程设计,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • 深入了解Java对象的克隆

    深入了解Java对象的克隆

    这篇文章主要介绍了Java对象的克隆的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08
  • Java Process类的详解及实例代码

    Java Process类的详解及实例代码

    这篇文章主要介绍了Java Process类的详解及实例代码的相关资料,需要的朋友可以参考下
    2017-02-02
  • Java二维数组与稀疏数组相互转换实现详解

    Java二维数组与稀疏数组相互转换实现详解

    在某些应用场景中需要大量的二维数组来进行数据存储,但是二维数组中却有着大量的无用的位置占据着内存空间,稀疏数组就是为了优化二维数组,节省内存空间
    2022-09-09
  • Java web实现账号单一登录,防止同一账号重复登录(踢人效果)

    Java web实现账号单一登录,防止同一账号重复登录(踢人效果)

    这篇文章主要介绍了Java web实现账号单一登录,防止同一账号重复登录,有点类似于qq登录踢人效果,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-10-10
  • kafka生产实践(详解)

    kafka生产实践(详解)

    下面小编就为大家带来一篇kafka生产实践(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • 关于通过Java连接mysql对反斜杠”\“转义的测试详解

    关于通过Java连接mysql对反斜杠”\“转义的测试详解

    这篇文章主要给大家介绍了关于通过Java连接mysql对反斜杠”\“转义的测试的相关资料,文中通过实例代码介绍的非常详细,对大家理解反斜杠”\“转义具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-06-06

最新评论