Java SPI机制原理——丑时

本文介绍ServiceLoader在Java中的作用,通过示例展示如何通过配置文件指定接口实现类,并利用ClassLoader将其加载到内存中,增强程序灵活性。深入分析ServiceLoader核心源代码,揭示其工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ServiceLoader源代码分析

1.ServiceLoader可以用来做什么

我比较喜欢用通俗的语言来说明,ServiceLoader我用通俗的语言来讲的话,就是可以通过配置文件灵活的来指定一个接口的实现类,再用ClassLoader来把这些实现类加载到内存中。这么做的好处是什么,这么做的好处是什么?显而易见,使得我们写的程序更加灵活,更加容易扩展。ServiceLoader是Java的SPI实现。

2.直接上个sample

定义一个接口:
在这里插入图片描述
两个实现类:
在这里插入图片描述
在这里插入图片描述
配置文件:
在这里插入图片描述

配置文件一定要放到META-INF/service 文件夹下面。并且文件名字必须是接口的包名加上类名(接口的全类名)

通过ServiceLoader来加载指定的实现类:
在这里插入图片描述

3.运行效果

在这里插入图片描述
我们可以把配置文件里面的com.smallcode.service.WxPayService删掉,执行的结果如下:
在这里插入图片描述
只剩下支付宝支付的字样了。
可以看出已经通过ServiceLoader把指定的实现类加载进去了。

3.ServiceLoader核心源代码分析

以上简单的演示下了ServiceLoader的效果,下面看下ServiceLoader核心源代码,来具体看下ServiceLoader是如何实现的。
沿着main方法里面的ServiceLoader.load方法追踪源代码:

// ServiceLoader.java
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

该方法调用了load(Class<S> service,ClassLoader loader) 方法:

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

调用了构造函数 ServiceLoader(Class<S> svc, ClassLoader cl) :

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

调用了reload() 方法:

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

追踪到一个LazyIterator,一个迭代器。
我们再看ServiceLoader 实现了 Iterable接口

 public final class ServiceLoader<S>
    implements Iterable<S>

当我们的在main方法里面通过foreach的时候,会调用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(); // <1>
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

这里涉及到迭代器原理和foreach,在这里不做详细介绍。

接着往下追踪代码:
在这里插入图片描述
在这里插入图片描述
拼接配置文件路径,可以看出路径是META-INF/services/接口全类名。
在这里插入图片描述
根据获取的路径读取文件,返回一个集合,配置文件每行对应集合一条数据。

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

在nextService(迭代器中next方法调用了nextService)可以看出,通过ClassLoader加载刚刚从配置文件里面获取到实现类。然后返回该实现类。
在这里插入图片描述

4.总结

本文首先简单的介绍了ServiceLoader,在编写了一个实例,最后通过源代码分析ServicerLoader具体的实现。总体来说ServiceLoader实现比较简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值