JDK SPI

简介

 SPI全名为Service Provider Interface
 作用:为接口自动寻找实现类
 实现方式:
  1.标准制定者制定接口
  2.不同厂商编写针对于该接口的实现类,
  并在jar的“classpath:META-INF/services/全接口名称”
  文件中指定相应的实现类全类名
  3.开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能

代码示例

Log接口

public interface Log {
    void execute();
}
复制代码

Log4j类

public class Log4j implements Log {
    @Override
    public void execute() {
        System.out.println("log4j...");
    }
}
复制代码

Logback类

public class Logback implements Log {
    @Override
    public void execute() {
        System.out.println("logback ...");
    }
}
复制代码

Main类

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.execute();
        }
    }
}
复制代码
在resources下创建META-INF/services目录
并创建Log全路径名的文件com.ye.spi.Log
文件里写实现类全路径,可以写多个
写多个时,可以自己实现逻辑选择对应实现
com.ye.spi.Log4j

直接启动Main类就可以得到想要的效果

源码解析

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.execute();
        }
    }
}
复制代码
1. ServiceLoader.load()
2. serviceLoader.iterator()
3. iterator.hasNext()
4. iterator.next()

第一个方法是ServiceLoader.load()

 先看ServiceLoader的属性
    private static final String PREFIX="META-INF/services/";//定义实现类的接口文件所在的目录
    private final Class service;//接口
    private final ClassLoader loader;//定位、加载、实例化实现类
    private final AccessControlContext acc;//权限控制上下文
    private LinkedHashMap providers = new LinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例>
    private LazyIterator lookupIterator;//真正进行迭代的迭代器
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
复制代码
    看上面两段代码,load方法最终是调用传参的构造函数new ServiceLoader<>(service, loader)
    再继续看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 = service;
            this.loader = loader;
        }    
复制代码
1. 赋值ServiceLoader的service
2. 赋值ServiceLoader的loader
3. 调用reload方法
4. 清空ServiceLoade的providers
5. new LazyIterator对象
综上所述,ServiceLoader.load()其实就是初始化了ServiceLoader对象和LazyIterator对象

第二个方法是serviceLoader.iterator()

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();
            }

        };
    }
复制代码
   获取迭代器,
   初始化knownProviders,第一次是空的
   实现了hasNext方法和next方法

iterator.hasNext()

public boolean hasNext() {
        if (knownProviders.hasNext())
            return true;
        return lookupIterator.hasNext();
    }
复制代码
先判断knownProviders有没有,如果有就返回true
如果没有调用lookupIterator.hasNext()
可以认为knownProviders是一个缓存,后面会看到什么时候有值的
先看一下lookupIterator即LazyIterator这个类的属性
    Class service;//接口
    ClassLoader loader;//类加载器
    Enumeration configs = null;//存放配置文件
    Iterator pending = null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称
    String nextName = null;//当前处理的实现类名称

LazyIterator.hasNext()方法

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            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;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
复制代码
其实调用的是LazyIterator.hasNextService()方法
1. nextName如果有,直接返回true
2. 找到对应目录文件
3. 解析文件内容
4. 赋值给nextName
5. 返回true

LazyIterator.next()

public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }
         
public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }  
        
 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 {
                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. 先判断knownProviders有没有,如果有就直接取
2. 调用nextService获取
3. 通过nextName加载对应的类,即实例化
4. 把实例化的对象放入providers中,下次就可以在这里面取,不用在实例化了
5. 返回实例化的对象

小结:spi大致流程就讲完了

1. 初始化 ServiceLoader和LazyIterator
2. 通过load传入的接口找到对应目录的文件
3. 解析文件找到类全路径名
4. 通过全路径名加载实例化类
5. 放入缓存并返回该对象

转载于:https://juejin.im/post/5c90d14ce51d45206a18befa

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值