java.util.ServiceLoader源码分析

回顾:

ServiceLoader类的使用(具体参考博客http://blog.csdn.net/liangyihuai/article/details/50716035):

使用步骤基本为:

1、编写一个接口(标准)。

2、编写接口的实现类。

3、在/src/META-INF/services下新建一个文件,文件名为接口的包名+接口名。

4、把实现类的全名写在该文件中,一个实现类占一行。

我们还知道ServiceLoader的原理是Java的反射机制。配置文件的作用是告知实现类是什么,怎么才能找到,然后通过反射机制创建相关的实现类。

下面我们就通过这个思路来分析ServiceLoader的源码。

public class Main {    public static void main(String[] args) {        ServiceLoader<Search> s = ServiceLoader.load(MyServer.class);
        Iterator<Search> searches = s.iterator();
        while (searches.hasNext()) {            Search curSearch = searches.next();
            curSearch.search("test");
        }
    }
}

上面这个代码是本人使用ServiceLoader的时候的Main函数。首先我们需要根据接口名字来load我们的实现类。

下面是源码:我们发现load方法返回的是一个自己new的ServiceLoader对象。我们看一下newServiceLoader<>(service, loader);的构造方法。

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

下面是构造方法:第一个参数是接口(上面的MyServer)的Class对象,第二个是类加载器。在这个构造函数中做了两件事,一是赋值,二是调用reload()方法。而reload方法的作用是根据接口的Class对象和类加载器来初始化lookupIterator对象。

private ServiceLoader(Class<S> svc, ClassLoader cl) {    service = svc;
    loader = cl;
    reload();
}
public void reload() {    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}providers对象的定义是:private LinkedHashMap<String,S> providers = new LinkedHashMap<>();key是实现类的全名字,value是实现类的对象,并且该对象已经被强转成接口的类型。

lookupIterator是LazyIterator类的对象,这个类是ServerLoader的私有内部类。它实现了迭代器Iterator,所以它具有迭代器的功能。同时它还定义了这几个属性:

Class<S>service;//接口的Class对象

ClassLoader loader;//类加载器
Enumeration<URL> configs = null;//保存实现类的URL
Iterator<String> pending = null;//保存实现类的全名
String nextName = null;//迭代器中下一个实现类的全名

调用reload方法之后,接着便是new了一个迭代器对象。这个是它的构造方法,作用只是初始化了两个属性。到了这个时候,还没有解析我们的配置文件,也还没有创建实现类的实例。这些是在什么时候是在那里触发的呢?在上面的main函数中,有searches.hasNext(),实际上调用的是LazyIterator类中的hasNext方法。

private LazyIterator(Class<S> service, ClassLoader loader) {    this.service = service;
    this.loader = loader;
}下面是hasNext方法的源码。我们注意到:String PREFIX = "META-INF/services/";service.getName()得到接口的全名(包名+接口名)。调用getResources(fullName)得到配置文件的URL集合(可能有多个配置文件)。通过这个方法parse(service, configs.nextElement());,参数是接口的Class对象和配置文件的URL来解析配置文件,返回值是配置文件里面的内容,也就是实现类的全名(包名+类名)。public boolean hasNext() {    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;
}下面的源码是解析配置文件,注意:返回值是一个数组,里面就是names,实现类的全名。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();
}

到了这里,已经解析了配置文件,知道了实现类的全名,接下来怎么办?我们需要利用Java的反射机制,根据类的全名类创建对象。那么在那里触发什么时候触发呢?看我们上面的main函数,当我们执行searches.next();的时候,也就是执行下面的代码:

下面是迭代器LazyIterator类中的方法。这个c = Class.forName(cn, false, loader);,第一个参数是实现类的全名,第三个是类加载器,返回值是实现类的Class对象。再看下面的S p = service.cast(c.newInstance());作用是创建实例并强转成接口的类型,最后返回。结束!谢谢! public S next() {        if (!hasNext()) {            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
    }
    public void remove() {        throw new UnsupportedOperationException();
    }
}

java.util.ServiceLoader源码分析相关推荐

  1. 并发编程5:Java 阻塞队列源码分析(下)

    上一篇 并发编程4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  2. Java并发-ReentrantReadWriteLock源码分析

    ReentrantLock实现了标准的互斥重入锁,任一时刻只有一个线程能获得锁.考虑这样一个场景:大部分时间都是读操作,写操作很少发生:我们知道,读操作是不会修改共享数据的,如果实现互斥锁,那么即使都 ...

  3. Java集合类框架源码分析 之 LinkedList源码解析 【4】

    上一篇介绍了ArrayList的源码分析[点击看文章],既然ArrayList都已经做了介绍,那么作为他同胞兄弟的LinkedList,当然必须也配拥有姓名! Talk is cheap,show m ...

  4. 【Java】equals源码分析

    源码分析 This class consists of static utility methods for operating on objects. These utilities include ...

  5. JAVA集合专题+源码分析

    文章目录 Java集合专题 集合和数组的区别 数组 集合 区别 集合体系结构介绍 单列集合 [Collection ] Collection接口 迭代器 迭代器原理 增强for循环 List接口 对集 ...

  6. java activerecord.db_JFinal 源码分析 [DB+ActiveRecord]

    我记得以前有人跟我说,"面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇 ...

  7. Java中ArrayList源码分析

    一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...

  8. Java容器 | 基于源码分析List集合体系

    一.容器之List集合 List集合体系应该是日常开发中最常用的API,而且通常是作为面试压轴问题(JVM.集合.并发),集合这块代码的整体设计也是融合很多编程思想,对于程序员来说具有很高的参考和借鉴 ...

  9. java并发:join源码分析

    join join join是Thread方法,它的作用是A线程中子线程B在运行之后调用了B.join(),A线程会阻塞直至B线程执行结束 join源码(只有继承Thread类才能使用) 基于open ...

最新文章

  1. Windows和Linux下apache-artemis-2.10.0安装配置
  2. (转)Linux下的输入/输出重定向
  3. 进程管理工具(Supervisor)笔记
  4. (转)Spring Boot 2 (七):Spring Boot 如何解决项目启动时初始化资源
  5. 特斯拉在美国召回947辆电动汽车 因倒车影像显示延迟
  6. 多线程 CreateThread与_beginthreadex本质区别
  7. 分享一个TEXT文档加密/解密编辑器
  8. [ERP/鼎捷E10][生产制造]取代料/替代料
  9. mysql jemalloc_jemalloc优化MySQL、Nginx内存管理
  10. 矩阵代数(四)- 分块矩阵
  11. win10 net framework 3.5提示错误代码0x800f081f
  12. 基于STM32的0.96寸OLED显示屏显示数据
  13. iframe标签使用
  14. 011235813用java写出来_C 编程练习题大全(带答案)
  15. JScript 06 根据成绩平均分划分等级
  16. 顺序查找与二分查找时间复杂度的比较
  17. gif图片体积过大怎么办?手把手教你快速压缩gif动图
  18. 查看 Windows 10 快捷键 占用 VSCode 多行编辑 ctrl alt uparrow
  19. 使用OpenSSL实现CA证书的搭建过程
  20. 数学计算机软件课程,《数学软件》课程教学大纲.doc

热门文章

  1. php多进程共享数据库,PHP多进程环境下通过共享内存与信号量实现资源共享
  2. java 捕获数据包,缓存从pcap捕获的数据包
  3. mysql windows 管道连接,科技常识:Windows Server 2016 MySQL数据库安装配置详细安装教程...
  4. c++ 高通、低通、带通滤波器_射频/微波滤波器
  5. mysql索引能重复吗_mysql重复索引与冗余索引实例分析
  6. python 修改字符串中的某个单词_python Pandas替换字符串中的单词
  7. solidworks模板_SolidWorks文件属性分类和创建方法,图纸自动属性的基础
  8. Android跨平台编译 —— BOOST
  9. Hazelcast集群服务(2)
  10. 转:软件设计漫谈之三:30分钟掌握面向对象类的设计原则