当前位置: 首页 > news >正文

Android中Spi机制的使用及源码原理解析

使用

  • 定义接口

    public interface IFunctionService {
        void test();
    }
    
  • 定义接口实现,这里实现了三个实现类。

public class FunctionService1 implements IFunctionService {
    private static final String TAG = "FunctionService1";

    @Override
    public void test() {
        Log.e(TAG, "test1: ");
    }
}


public class FunctionService2 implements IFunctionService {
    private static final String TAG = "FunctionService2";
    @Override
    public void test() {
        Log.e(TAG, "test2: " );
    }
}

public class FunctionService3 implements IFunctionService {
    private static final String TAG = "StrategyService3";

    @Override
    public void test() {
        Log.e(TAG, "test3: " );
    }
}

  • 编写配置文件,在src/main文件下创建resources/META-INF/services文件夹,然后在其中创建文件,文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔,如下:com.example.testspi.IFunctionService

    注意:META-INF/services/文件路径不可以改变,这个在代码中写死的。

配置文件路径

文件的内容如下:

com.example.testspi.FunctionService1
com.example.testspi.FunctionService2
com.example.testspi.FunctionService3
  • 测试方法
 public static void main(String[] args) {
        ServiceLoader<IFunctionService> load = ServiceLoader.load(IFunctionService.class);

        for (IFunctionService iFunctionService : load) {
            iFunctionService.test();
        }
    }

概述

简介

服务提供者接口(Service Provider Interface,简写为SPI)是JDK内置的一种服务提供发现机制。可以用来加载框架扩展和替换组件,主要是被框架的开发人员使用。在java.util.ServiceLoader的文档里有比较详细的介绍。

Java中SPI机制主要思想是将装配的控制权移到程序之外,是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,有点类似Spring的IOC机制。在模块化设计中这个机制尤其重要,其核心思想就是解耦。

规范约束

当服务的提供者,提供了服务接口的某种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能实现服务接口与实现的解耦。

缺点

  1. 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

  2. 多个并发多线程使用ServiceLoader类的实例是不安全的。

  3. 扩展如果依赖其他的扩展,做不到自动注入和装配。

源码解析

今天按照方法的调用过程对源码进行分析。

  • 先看一下ServiceLoader对应的全局变量
public final class ServiceLoader<S> implements Iterable<S> {
    //接口配置文件对应的路径。
	private static final String PREFIX = "META-INF/services/";

    // 表示正在加载的服务的类或接口
    private final Class<S> service;

    // 用于定位、加载和实例化提供程序的类加载器
    private final ClassLoader loader;

   	//创建ServiceLoader时采用的访问控制上下文
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();

    // 懒加载迭代器
    private LazyIterator lookupIterator;


................
}
  • load()

    为给定的服务类型创建一个新的服务加载器。

    public static <S> ServiceLoader<S> load(Class<S> service) {
        //获取类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
  • 调用重载方法load

    public static <S> ServiceLoader<S> load(Class<S> service,
                                                ClassLoader loader) {
            return new ServiceLoader<>(service, loader);
        }
    
  • 创建ClassLoader对象

     private ServiceLoader(Class<S> svc, ClassLoader cl) {
         //其实就是svc
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
         
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        
            reload();
        }
    
  • 调用reload

    清除此加载器的实现类对象缓存,以便重新加载所有实现类对象。

        public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    
  • LayIterator创建

    LazyIterator是一个迭代器,查找实现类和创建实现类的过程,都在LazyIterator完成。

     private class LazyIterator implements Iterator<S> {
    
            Class<S> service;
            ClassLoader loader;
            Enumeration<URL> configs = null;
            Iterator<String> pending = null;
            String nextName = null;
    
            private LazyIterator(Class<S> service, ClassLoader loader) {
                this.service = service;
                this.loader = loader;
            }
    
  • 迭代

    当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

        public Iterator<S> iterator() {
            return new Iterator<S>() {
    
                Iterator<Map.Entry<String, S>> knownProviders
                        = providers.entrySet().iterator();
    
                public boolean hasNext() {
                    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();
                }
    
            };
        }
    
  • hasNextService

           private boolean hasNextService() {
               //第二次调用的时候,已经解析完成了,直接返回
                if (nextName != null) {
                    return true;
                }
               //如果读取过则不在重新读取
                if (configs == null) {
                    try {
                        //配置文件路径,加上接口的全限定类名,就是文件服务类的文件
                        String fullName = PREFIX + service.getName();
                        //从路径中读取配置,将文件路径转成URL对象
                        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;
                    }
                    //解析URL文件对象,读取内容,最后返回
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    
  • nextService

    
            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,
                            // Android-changed: Let the ServiceConfigurationError have a cause.
                            "Provider " + cn + " not found", x);
                    // "Provider " + cn + " not found");
                }
                if (!service.isAssignableFrom(c)) {
                    // Android-changed: Let the ServiceConfigurationError have a cause.
                    ClassCastException cce = new ClassCastException(
                            service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
                    fail(service,
                            "Provider " + cn + " not a subtype", cce);
                    // 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
            }
    

总结

  1. 调用load方法后本质是读取配置文件,动态的加载配置文件中的实现类。
  2. 使用了懒加载的机制,只有在遍历寻找时才会创建对象。

相关文章:

  • 风控模型黑箱可解释,试下这个方法来演示
  • Shader Graph的用法初探
  • unity core-prefab
  • rollup常用插件详解
  • 基于Solidworks的三维光路结构示意图绘制实例演示-技术细节
  • Day726.Java平台模块系统 -Java8后最重要新特性
  • 虚机的部分磁盘空间被谁吃了?
  • leetcode88. 合并两个有序数组
  • 【python 】pygame制作简单的游戏移动操作
  • 软件流程和管理(五):Stakeholder Management Communication
  • JavaScript 案例一 --bind,call,apply
  • 计算机毕业设计django基于python金太阳家居电商平台(源码+系统+mysql数据库+Lw文档)
  • Crypto Pragmatist:5个值得关注的加密叙事
  • SpringBoot配置文件
  • 幸福心理与抗逆力培养的工控系统安全实验课程研究
  • JavaScript 如何正确处理 Unicode 编码问题!
  • 【Leetcode】104. 二叉树的最大深度
  • 2019年如何成为全栈工程师?
  • Asm.js的简单介绍
  • canvas 高仿 Apple Watch 表盘
  • Fundebug计费标准解释:事件数是如何定义的?
  • Github访问慢解决办法
  • nodejs:开发并发布一个nodejs包
  • Object.assign方法不能实现深复制
  • Web标准制定过程
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 从零开始在ubuntu上搭建node开发环境
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 设计模式(12)迭代器模式(讲解+应用)
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 首页查询功能的一次实现过程
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 小程序button引导用户授权
  • 做一名精致的JavaScripter 01:JavaScript简介
  • 国内开源镜像站点
  • 说说我为什么看好Spring Cloud Alibaba
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • (04)odoo视图操作
  • (12)Hive调优——count distinct去重优化
  • (145)光线追踪距离场柔和阴影
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (离散数学)逻辑连接词
  • (强烈推荐)移动端音视频从零到上手(下)
  • (原)本想说脏话,奈何已放下
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)Windows2003安全设置/维护
  • (转)大型网站架构演变和知识体系
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • ***测试-HTTP方法
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution