java SPI 源码分析(javac源码分析)

JDK的SPI用法

1,创建com.test.Log接口,创建实现类com.test.impl.Log4j,com.test.impl.Logback

2,在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件。比如com.test.Log

3,文件内容:

com.test.impl.Log4j
com.test.impl.Logback

4,JDK的spi在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。

5,用法:

# 创建服务加载器
ServiceLoader<Log> serviceLoader =  ServiceLoader.load(Log.class);
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

jdk11下源码解析

1,创建服务加载器

ServiceLoader<Log> serviceLoader =  ServiceLoader.load(Log.class);

2,执行serviceLoader.iterator();

public Iterator<S> iterator() {
    // create lookup iterator if needed
    if (lookupIterator1 == null) {
        lookupIterator1 = newLookupIterator();
    }
    # 创建一个迭代器匿名实现类
    return new Iterator<S>() {
        // record reload count
        final int expectedReloadCount = ServiceLoader.this.reloadCount;
        // index into the cached providers list
        int index;
        /**
         * Throws ConcurrentModificationException if the list of cached
         * providers has been cleared by reload.
         */
        private void checkReloadCount() {
            if (ServiceLoader.this.reloadCount != expectedReloadCount)
                throw new ConcurrentModificationException();
        }
        @Override
        public boolean hasNext() {
            checkReloadCount();
            if (index < instantiatedProviders.size())
                return true;
            return lookupIterator1.hasNext();
        }
        @Override
        public S next() {
            checkReloadCount();
            S next;
            if (index < instantiatedProviders.size()) {
                next = instantiatedProviders.get(index);
            } else {
                next = lookupIterator1.next().get();
                instantiatedProviders.add(next);
            }
            index++;
            return next;
        }
    };
}

一 执行serviceLoader.iterator()->hasNext()->lookupIterator1.hasNext();

二 执行LazyClassPathLookupIterator#hasNext()->hasNextService();

->nextProviderClass()

1 Enumeration<URL> configs = loader.getResources(fullName); 加载资源

fullName="META-INF/services/com.study.utils.spi.demo.KeyGenerator"

2 遍历configs ,解析url 资源

pending = parse(configs.nextElement());

private Iterator<String> parse(URL u) {
    Set<String> names = new LinkedHashSet<>(); // preserve insertion order
    try {
        # 打开连接
        URLConnection uc = u.openConnection();
        uc.setUseCaches(false);
        try (InputStream in = uc.getInputStream();
             BufferedReader r
                 = new BufferedReader(new InputStreamReader(in, "utf-8")))
        {
            int lc = 1;
            while ((lc = parseLine(u, r, lc, names)) >= 0);
        }
    } catch (IOException x) {
        fail(service, "Error accessing configuration file", x);
    }
    return names.iterator();
}

解析url 资源,读取每一行,放入Set集合。返回Set#iteator()

部分源码:

while ((pending == null) || !pending.hasNext()) {
    if (!configs.hasMoreElements()) {
        return null;
    }
    # 解析url 资源,读取每一行,放入Set集合。返回Set#iteator()
    pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
    # 得到 Class对象。
    return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
    fail(service, "Provider " + cn + " not found");
    return null;
}

也就是说:

执行LazyClassPathLookupIterator#hasNext()->hasNextService()->nextProviderClass()

得到了META-INF/services/目录下以com.test.Log为文件名,解析其内容得到了Class对象。

扩展

class1.isAssignableFrom(class2) 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。

hasNextService()部分源码

Class<?> clazz = nextProviderClass();
if (clazz == null)
    return false;
if (clazz.getModule().isNamed()) {
    // ignore class if in named module
    continue;
}
if (service.isAssignableFrom(clazz)) {
    Class<? extends S> type = (Class<? extends S>) clazz;
    Constructor<? extends S> ctor
        = (Constructor<? extends S>)getConstructor(clazz);
    ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
    nextProvider = (ProviderImpl<T>) p;
} else {
    fail(service, clazz.getName() + " not a subtype");
}

Class的构造器对象封装到Provider<T> nextProvider 对象中了。也就是说调用hasNext()方法,得到了nextProvider 对象。(相当于得到了Class的构造器对象)

调用next()方法

->LazyClassPathLookupIterator#next()

源码解析:next = lookupIterator1.next().get();

public S next() {
    checkReloadCount();
    S next;
    if (index < instantiatedProviders.size()) {
        next = instantiatedProviders.get(index);
    } else {
        # 第一次遍历进入这里
        next = lookupIterator1.next().get();
        # 缓存对象
        instantiatedProviders.add(next);
    }
    index++;
    return next;
}
# lookupIterator1.next()下
Provider<T> provider = nextProvider;
if (provider != null) {
    nextProvider = null;
    return provider;
}
# lookupIterator1.next().get()
# provider#get
public S get() {
    if (factoryMethod != null) {
        return invokeFactoryMethod();
    } else {
        # 创建对象
        return newInstance();
    }
}

总结:

1,调用hasNext() 会读配置文件内容,创建构造器对象,放入一个临时provider对象中

2,调用next()方法,会从临时provider对象中,取出构造器对象,创建配置文件内容中的对象实例。

3,ServiceLoader一旦load了某个接口,然后next() 取出了某个对象实例,那么就会缓存起来。缓存到instantiatedProviders对象中。

// The lazy-lookup iterator for iterator operations
private Iterator<Provider<S>> lookupIterator1;
// 缓存已经加载的对象。
private final List<S> instantiatedProviders = new ArrayList<>();

思路:读取classPath下配置文件中内容,反射生成类。根据不同的class对象,获取不同的类。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注