zoukankan      html  css  js  c++  java
  • ServiceLoader(二)源码解析

    源码分析

    java作为一个把interface看得很重要的语言,发展出了各种自动发现机制。比如eureka服务发现,slf4j日志自动发现日志实现,亦或者jdbc定义的接口自动根据URL选择对应的jdbc实现等。自动发现机制将接口和实现很好地隔离开,使得代码高度解耦合,是一种非常好地设计思路。

    前面文章中,我们简单了解了java内置的自动发现(ServiceLoader)是怎么使用的:https://www.cnblogs.com/lay2017/p/11194625.html

    本文接续使用的内容,将打开ServiceLoader的源代码,阅读一下它的加载逻辑,为此,我们从以下这两行代码开始

    ServiceLoader<Speaker> speakers = ServiceLoader.load(Speaker.class);
    speakers.forEach(Speaker::say);

    ServiceLoader.load()加载配置

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

    继续跟进load,可以看到实例化了一个ServiceLoader的实例

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

    继续跟进构造方法,重点落在了reload方法上

    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方法,providers缓存了已经加载的实现类,以全路径名作为key。

    clear方法在每次load的时候作清空,然后初始化一个Iterator迭代器(Lazy),可以想象后面的代码就是迭代这个迭代器,迭代一个加载一个实现类

    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    再进入LazyIterator构造方法看看,单纯的赋值操作

    Class<S> service;
    ClassLoader loader;
    
    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    ServiceLoader方法就是单纯地清空原有地缓存,然后初始化一个迭代器而已,比较简单。

    迭代LazyIterator

    ServiceLoader方法初始化LazyIterator,那么我们看看这个迭代器地迭代过程做了什么。

    hasNextService方法

    首先看看判断是否有下一个Service的方法

    private static final String PREFIX = "META-INF/services/";
    
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;
    
    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
    
        // 从类路径加载配置文件地URL
        if (configs == null) {
            try {
                // 拼接全路径 META-INF/services/接口类全路径名
                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;
            }
            // 迭代URL地址,获取实现类的全路径名
            pending = parse(service, configs.nextElement());
        }
        // 将获取到的全路径名作为下一个加载的类全路径名
        nextName = pending.next();
        return true;
    }

    hasNextService先根据接口类的全路径名获取了所有配置的URL地址

    然后迭代一个URL地址,通过parse方法解析出下一个待加载的配置文件中包含的实现类集合。我们跟进parse方法,看看它是怎么把URL地址解析成全路径配置的。

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

    parseLine执行一次,则解析一行配置,也就获得一个实现类的全路径,跟进parseLine看看

    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError{
        // 读取1行
        String ln = r.readLine();
        if (ln == null) {
            // 空文件
            return -1;
        }
    
        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('	') >= 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);
            }
            // 先从缓存判断是否存在该实现类,再从names中判断是否包含(二者加在一起,防止相同配置文件重复配置,也防止不同配置文件重复配置)
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        // 下一行
        return lc + 1;
    }

    readline一行一行地读取配置文件的实现类配置,如果没有被加载过,那么就添加为本次待加载地类。

    hasNextService方法,其实就是从配置文件中不断地读取配置到集合当中,供nextService使用

    nextService加载实现类

    hasNextService做了前置工作,获取了全路径名供迭代。而nextService方法将负责加载该类到虚拟机当中。我们打开nextService看看

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

    方法比较简单,单纯地通过Class.forName加载类,并反射获取实例对象强转了一次,并返回。

    总结

    ServiceLoader作为JAVA内置地spi机制,使用起来非常地简单。如同前面文章说的,其实就是面向接口 + 配置文件 + 多态,我们再一次见到了java从面向对象不断延伸地东西有多么灵活、干净。

  • 相关阅读:
    数据库DQL(Data Query Language)语言学习之三:排序查询
    数据库DQL(Data Query Language)语言学习之二:条件查询
    MySQL中的通配符
    SQL查询文档网站
    python之特殊方法
    java之静态函数和静态变量
    java之类和对象
    python之类的继承
    python的面向对象编程
    python之模块(在命令行当中使用pip install 模块来进行模块的安装)
  • 原文地址:https://www.cnblogs.com/lay2017/p/12182983.html
Copyright © 2011-2022 走看看