Dubbo SPI(一)

在讨论Dubbo spi之前 首先了解一下Java spi机制

什么是Java的spi

SPI(Service Provider Interface)是一种服务发现机制。SPI的本质是将接口实现类的全限定名写在一个文件中,由加载器去加载这些接口实现类,这样可以在运行时动态将接口替换为实现类,通过SPI机制可以拓展功能,且SPI机制在框架中也有所应用。

Java spi示例

首先定义一个接口:

public interface Hello {
    void say();
}

为该接口添加两个实现类:

public class HelloImpl1 implements Hello {

    @Override
    public void say() {
        System.out.println("我是实现类1");
    }
}

public class HelloImpl2 implements Hello {

    @Override
    public void say() {
        System.out.println("我是实现类2");
    }
}

在resource的META-INF下面创建文件夹services,并创建名称为接口全限定名的文件:

com.lgx.study.spi.Hello文件

文件内容为:

com.lgx.study.spi.HelloImpl1
com.lgx.study.spi.HelloImpl2

来测试一下:

public class Test {

    public static void main(String[] args) {
        ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);
        Iterator<Hello> iterator = loader.iterator();
        while (iterator.hasNext()){
            Hello next = iterator.next();
            next.say();
        }
    }
}

运行结果:
我是实现类1
我是实现类2

我们从源码进行简单分析:

ServiceLoader loader = ServiceLoader.load(Hello.class);这行代码做了什么事呢?

#ServiceLoader

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

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();
        // LazyIterator是ServiceLoader的内部类实现了Iterator接口
        lookupIterator = new LazyIterator(service, loader);
}

可以看到一段代码比较简单就是创建了一个ServiceLoader,并将类加载器和接口的Class类传入进行实例化,并创建了一个迭代器

再看 这一行 : Iterator iterator = loader.iterator();

#ServiceLoader
    
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

接下来进入while循环中:
iterator.hasNext():

public boolean hasNext() {
    if (knownProviders.hasNext())
        return true;
    // 进入到这一步中
    return lookupIterator.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 {
            // PREFIX为:META-INF/services
            // service.getName()为:com.lgx.study.spi.Hello
            // 这里也就解释了为什么我们要在META-INF/services下面创建名称为接口
            // 全限定名的文件
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
            // 从resource下加载该文件
                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;
}

private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1; 
            // 在这里做文件解析 继续往下跟
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
}

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        ......
        // 以下为具体的解析过程 
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            // 解析完的ln为com.lgx.study.spi.HelloImpl1和com.lgx.study.spi.HelloImpl2
            // 将解析出来的实现类的权限名加入到List中
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
}

解析完了返回Iterator

接着是调用 iterator.next()拿到接口:

public S next() {
    if (knownProviders.hasNext())
        return knownProviders.next().getValue();
    // 来到这里  继续往下走
    return lookupIterator.next();
}

public S next() {
    if (acc == null) {
        // 继续走
        return nextService();
    } 
    ......
}
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // 这里的cn为实现类的全权限名:com.lgx.study.spi.HelloImpl1和com.lgx.study.spi.HelloImpl2
    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());
        // 加入到map中缓存:
        // map<com.lgx.study.spi.HelloImpl1, HelloImpl1实例>
        // map<com.lgx.study.spi.HelloImpl2, HelloImpl2实例>
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

到这里所有的代码都看完了,其实看起来也很简单:

通过加载META-INFO/services下接口文件,解析文件内的每一行内容放入集合中,再遍历这个集合,拿到实现类的权限定名,并使用类加载器进行加载,最后创建该类的实例加入到缓存中,最后返回改实例。

posted @ 2021-02-20 14:40  丁茜萌萌哒  阅读(27)  评论(0编辑  收藏  举报