• 【Java】深入理解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机制

  • 相关阅读:
    配置svn
    驱动开发问题error 2019: __ftol2_sse 找不到的错误哦
    ollydbg,ring3级别的调试软件
    IoAttachDevice源码
    新手搭建 eclipse+winDDK驱动开发平台
    IoCallDriver源码剖析
    com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
    error LNK2019: 无法解析的外部符号 "int __cdecl wsprintf
    (转)3ds Max 和 Away3D工作流程
    页游客户端热更新时获取的是旧资源的处理办法
  • 原文地址:https://www.cnblogs.com/xcmelody/p/10859704.html
Copyright © 2020-2023  润新知