欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【Java】深入理解Java中的spi机制

程序员文章站 2022-07-10 23:42:24
深入理解Java中的spi机制 全名为 是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 = 基于接口的编程+策略模式+配置文件 的动态加载机制 Java SPI的具体约定如下: 当服务的提供者,提供了服务接口的一种实现之后 ......

深入理解java中的spi机制

spi全名为service provider interface是jdk内置的一种服务提供发现机制,是java提供的一套用来被第三方实现或者扩展的api,它可以用来启用框架扩展和替换组件。

java spi = 基于接口的编程+策略模式+配置文件 的动态加载机制

java spi的具体约定如下:

当服务的提供者,提供了服务接口的一种实现之后,在jar包的meta-inf/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。

而当外部程序装配这个模块的时候,就能通过该jarmeta-inf/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

根据spi的规范我们的服务实现类必须有一个无参构造方法

为什么一定要在classes中的meta-inf/services下呢?

jdk提供服务实现查找的一个工具类:java.util.serviceloader

在这个类里面已经写死

// 默认会去这里寻找相关信息
private static final string prefix = "meta-inf/services/";

常见的使用场景:

  • jdbc加载不同类型的数据库驱动
  • 日志门面接口实现类加载,slf4j加载不同提供商的日志实现类
  • spring中大量使用了spi,
    • servlet3.0规范
    • servletcontainerinitializer的实现
    • 自动类型转换type conversion spi(converter spi、formatter spi)
  • dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现

简单的spi实例

整体包结构如下

└─main
    ├─java
    │  └─com
    │      └─xinchen
    │          └─spi
    │              └─app.java
    │              └─iservice.java
    │              └─serviceimpla.java
    │              └─serviceimplb.java            
    └─resources
        └─meta-inf
            └─services
                └─com.xinchen.spi.iservice

spi接口

public interface iservice {
    void say(string word);
}

具体实现类

public class serviceimpla implements iservice {
    @override
    public void say(string word) {
        system.out.println(this.getclass().tostring() + " say: " + word);
    }
}

public class serviceimplb implements iservice {
    @override
    public void say(string word) {
        system.out.println(this.getclass().tostring() + " say: " + word);
    }
}

/resource/meta-inf/services/com.xinchen.spi.iservice

com.xinchen.spi.serviceimpla
com.xinchen.spi.serviceimplb

client类

public class app {
    static serviceloader<iservice> services = serviceloader.load(iservice.class);

    public static void main(string[] args) {
        for (iservice service:services){
            service.say("hello world!");
        }
    }
}

//    结果:
//    class com.xinchen.spi.serviceimpla say: hello world!
//    class com.xinchen.spi.serviceimplb say: hello world!

源码解析

java.util.serviceloader中的fied区域

    // 加载具体实现类信息的前缀
    private static final string prefix = "meta-inf/services/";

    // 需要加载的接口
    // the class or interface representing the service being loaded
    private final class<s> service;

    // 用于加载的类加载器
    // the class loader used to locate, load, and instantiate providers
    private final classloader loader;

    // 创建serviceloader时采用的访问控制上下文
    // the access control context taken when the serviceloader is created
    private final accesscontrolcontext acc;

    // 用于缓存已经加载的接口实现类,其中key为实现类的完整类名
    // cached providers, in instantiation order
    private linkedhashmap<string,s> providers = new linkedhashmap<>();

    // 用于延迟加载接口的实现类
    // the current lazy-lookup iterator
    private lazyiterator lookupiterator;

serviceloader.load(iservice.class)进入源码中

    public static <s> serviceloader<s> load(class<s> service) {
        // 获取当前线程上下文的类加载器
        classloader cl = thread.currentthread().getcontextclassloader();
        return serviceloader.load(service, cl);
    }

serviceloader.load(service, cl)

    public static <s> serviceloader<s> load(class<s> service,classloader loader){
        // 返回serviceloader的实例
        return new serviceloader<>(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();
    }

    public void reload() {
        // 清空已经缓存的加载的接口实现类
        providers.clear();
        // 创建新的延迟加载迭代器
        lookupiterator = new lazyiterator(service, loader);
    }   
    
    private lazyiterator(class<s> service, classloader loader) {
        // 指定this类中的 需要加载的接口service和类加载器loader
        this.service = service;
        this.loader = loader;
    }         

当我们通过迭代器获取对象实例的时候,首先在成员变量providers中查找是否有缓存的实例对象

如果存在则直接返回,否则则调用lookupiterator延迟加载迭代器进行加载

迭代器判断的代码如下

public iterator<s> iterator() {
        // 返回迭代器
        return new iterator<s>() {
            // 查询缓存中是否存在实例对象
            iterator<map.entry<string,s>> knownproviders
                = providers.entryset().iterator();

            public boolean hasnext() {
                // 如果缓存中已经存在返回true
                if (knownproviders.hasnext())
                    return true;
                // 如果不存在则使用延迟加载迭代器进行判断是否存在
                return lookupiterator.hasnext();
            }

            public s next() {
                // 如果缓存中存在则直接返回
                if (knownproviders.hasnext())
                    return knownproviders.next().getvalue();
                // 调用延迟加载迭代器进行返回
                return lookupiterator.next();
            }

            public void remove() {
                throw new unsupportedoperationexception();
            }

        };
    }

lazyiterator的类加载

        // 判断是否拥有下一个实例
        private boolean hasnextservice() {
            // 如果拥有直接返回true
            if (nextname != null) {
                return true;
            }

            // 具体实现类的全名 ,enumeration<url> config
            if (configs == null) {
                try {
                    string fullname = prefix + service.getname();
                    if (loader == null)
                        configs = classloader.getsystemresources(fullname);
                    else
                        configs = loader.getresources(fullname);
                } catch (ioexception x) {
                    fail(service, "error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasnext()) {
                if (!configs.hasmoreelements()) {
                    return false;
                }
                // 转换config中的元素,或者具体实现类的真实包结构
                pending = parse(service, configs.nextelement());
            }
            // 具体实现类的包结构名
            nextname = pending.next();
            return true;
        }

        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 {
                // 通过c.newinstance()实例化
                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类的实例是不安全的

  • 虽然serviceloader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。

参考

the java™ tutorials

聊聊dubbo(五):核心源码-spi扩展

深入理解java spi机制