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. 使用了懒加载的机制,只有在遍历寻找时才会创建对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈德山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值