SPI原理解析一

作者: yueyue_projects | 来源:发表于2018-08-29 20:44 被阅读5次

    SPI(service provider Interfaces),对于Java中的实现SPI类ServiceLoader来讲,就是Java类库里的代码能够调用外部实现类的代码。听起来是不是很抽象。那从原理上来讲如何实现呢?内部调用外部实现类又有什么用呢?在这里我先回答第二个问题:

    Q:内部调用外部实现类又有什么用?
    A: 我认为有下面两点原因

    • 试想JDK提供一个接口interface Driver{},但是它不想自己实现,因为没有一个固定的实现方式,比如mysql中的数据库驱动加载。于是这个接口留给实现者实现。然后JDK在内部为执行一系列的接口方法(注意这个接口已经被外部类实现,至于如何加载外部类,这就是JDK里的ServiceLoader做的事情了),就能提供灵活的实现方式。
    • 运用上面的思想,我们可以实现A模块引用B模块,A可以调用B中的任何函数,且B模块也能实现调用A模块的函数。实现方式是B(可以类比上述JDK)提供一个接口Interface IB,然后让A模块去实现这个接口。最后通过SPI技术,B能够加载A中的实现类,从而第调用了A中实现类的函数

    Q:SPI技术原理是什么呢?
    A:运用类加载技术,根据上面想法,我们可知关键点在于B如何加载A中的实现类的。从系统思维角度来讲A引用了B,B就肯定会获得A的某些信息。废话不说,直接上如何实现这样场景的代码:
    先看项目路径


    image.png

    child1的实现:
    定义接口

    package spi;
    public interface DemoService {
        public String sayHi(String msg);
    }
    

    META-INF.services/spi.demoService写如下代码,表示实现类的路径,实现类必须要按照该字段定义的路径来

    spi.ChineseDemoServiceImpl
    spi.EnglishDemoServiceImpl
    

    spiMain.java:该类是实现child1调用child2的重要类

    package spi;
    
    import java.util.Iterator;
    import java.util.ServiceLoader;
    
    public class SpiMain {
    
        public static void getConnection(){
            ServiceLoader<DemoService> serviceLoader = ServiceLoader.load(DemoService.class);
            Iterator<DemoService> services = serviceLoader.iterator();
            while (services.hasNext()) {
                DemoService service = services.next();
                System.out.println(service.sayHi("world"));
            }
        }
    
    }
    

    我们需要讲child1模块打包成.jar格式放到child2模块中。
    child2:
    下面是两个接口实现类,当然我也可以在这个类里面做其他事情,以便child1模块可以更多的调用
    ChineseDemoServiceImpl.java

    package spi;
    
    public class ChineseDemoServiceImpl implements DemoService {
    
        @Override
        public String sayHi(String msg) {
            return "你好" + msg;
        }
    }
    

    EnglishDemoServiceImpl.java

    package spi;
    public class EnglishDemoServiceImpl implements DemoService {
        @Override
        public String sayHi(String msg) {
            return "hello" + msg;
        }
    }
    

    child2Main.java

    package spi;
    public class Child2Main {
        public static void main(String[] args) {
            SpiMain.getConnection();//调用child1的函数,初始化自己提供的服务实现类
        }
    }
    

    从以上代码,我们就能比较具体的看到SPI的作用了。也能回答之前提出的问题了,是通过在文件里指定路径名的方式,去搜索的实现类。但是问题在于ServiceLoader是JDK里面的呀,是通过Extension ClassLoader加载的呀,这个加载器如何去加载应该有App ClassLoader加载的.class文件呢?原来其引入了一个线程上下文类加载器(该加载器是App ClassLoader),强制破坏了双亲委派模型层层加载的原则,在ServiceLoader里直接请求上下文加载器去加载指定路径的.class文件。

    好了,这一节先说到这里,我会在原理解析二中,详细展开ServiceLoader的源代码。

    源码github:https://github.com/zhouyueyuedsf/spidemo

    相关文章

      网友评论

        本文标题:SPI原理解析一

        本文链接:https://www.haomeiwen.com/subject/dxnkwftx.html