Java SPI

Posted 水田如雅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java SPI相关的知识,希望对你有一定的参考价值。

what SPI?

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

如何使用

定义如下类:

public interface SPITestInterface 
    String consoleSth(String source);


public class DefaultSPITestInterfaceImplOne implements SPITestInterface 
    @Override
    public String consoleSth(String source) 
        return source;
    

public class DefaultSPITestInterfaceImplTwo implements SPITestInterface 
    @Override
    public String consoleSth(String source) 
        if (source == null || source.length() < 1) 
            return "";
        
        return source.toUpperCase();
    

之后在resource文件夹下,添加文件:

文件名称为接口的包名,文件内容为:

com.common.lib.demo.javasource.DefaultSPITestInterfaceImplOne
com.common.lib.demo.javasource.DefaultSPITestInterfaceImplTwo

外部调用:

@Test
    public void consoleSth() 
        ServiceLoader<SPITestInterface> testInterfaces = ServiceLoader.load(SPITestInterface.class);
        for (SPITestInterface testInterface : testInterfaces) 
            System.out.println(testInterface.consoleSth("aaaaaBBBB"));;
        
    

输出:

aaaaaBBBB
AAAAABBBB

实现解析

首先,扫描的配置文件路径。

 public static <S> ServiceLoader<S> load(Class<S> service) 
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    

加载的时候,会使用当前线程class loader.

构造:

 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();
    

这里主要是会构造个懒加载器,由这个东西解析文件,并加载类。
在懒加载器里面:

 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
        

实例化的类都会放入:

  // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

总结

  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。

这种机制常用在,例如,java提供一些接口,例如,java提供数据访问的接口,我们引入mysql connect的包,来实现对mysql的访问操作,在mysql-connector中,加入对mysql数据库访问的实现,就是通过SPI实现的。

这种可插拔形式,在架构设计中应用广泛。

以上是关于Java SPI的主要内容,如果未能解决你的问题,请参考以下文章

java SPI 04-spi dubbo 实现源码解析

java SPI 07-自动生成 SPI 配置文件实现方式

Java SPI 与 dubbo SPI

java SPI 01-SPI 是什么?spi 使用入门教程 ServiceLoader 使用简介

java SPI 03-ServiceLoader jdk 源码解析

java SPI 03-ServiceLoader jdk 源码解析