Analysis on the source code of serviceloader in JDK

premise

Immediately after the previous article "analysis of resource loading in JDK through source code", serviceloader is the core class of service class loading in SPI (service provider interface). That is, this article first introduces the use of serviceloader, and then analyzes its source code.

Use of serviceloader

Here is a classic example. The Java driver of MySQL is loaded through serviceloader. First, the dependency of MySQL connector Java is introduced:

<dependency>
    <groupId>MysqL</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

Check the meta-inf directory under the dependent source package. You can see:

Let's move on to Java Lang. drivermanager, the static code block contains:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

Among them, you can view the following code snippet of loadinitialdrivers():

java. Lang. drivermanager is the basic class for starting class loader loading, but it can load classes other than rt.jar package. As mentioned in the previous article, the two parent delegation model is broken here because the thread context class loader is used in serviceloader to load classes. Here, the process of JDBC loading is the typical use of SPI. The summary rules are as follows:

For a simple example, first define an interface and two implementations:

public interface Say {

  void say();
}

public class SayBye implements Say {

	@Override
	public void say() {
		System.out.println("Bye!");
	}
}

public class SayHello implements Say {

	@Override
	public void say() {
		System.out.println("Hello!");
	}
}

Then add the following files to the meta inf / services of the project:

Finally, verify through the main function:

Based on SPI or serviceloader loading interface implementation, this method can also be widely used in relatively basic components, because it is a mature specification.

Serviceloader source code analysis

The above introduces the use of serviceloader through a classic example and an example, and then we deeply analyze the source code of serviceloader. Let's first look at the class signature and attribute definition of serviceloader:

public final class ServiceLoader<S> implements Iterable<S>{
    //需要加载的资源的路径的目录,固定是ClassPath下的Meta-INF/services/
    private static final String PREFIX = "Meta-INF/services/";
    // ServiceLoader需要正在需要加载的类或者接口
    // The class or interface representing the service being loaded
    private final Class<S> service;
    // ServiceLoader进行类加载的时候使用的类加载器引用
    // The class loader used to locate,load,and instantiate providers
    private final ClassLoader loader;
    // 权限控制上下文
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    //基于实例的顺序缓存类的实现实例,其中Key为实现类的全限定类名
    // Cached providers,in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 当前的"懒查找"迭代器,这个是ServiceLoader的核心
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

    //暂时忽略其他代码...
}    

Serviceloader implements the iteratable interface, which reminds us that we need to focus on the implementation of the iterator () method when analyzing its source code. Serviceloader relies on the class loader instance for class loading. Its core attribute lazyitterer is used to implement the iterator () method, which will be analyzed later. Next, we analyze the constructor of serviceloader:

public void reload() {
    //清空缓存
    providers.clear();
    //构造LazyIterator实例
    lookupIterator = new LazyIterator(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();
}

Serviceloader only has a private constructor, that is, it cannot be instantiated through the constructor, but to instantiate serviceloader, it must rely on its static method to call the private constructor to complete the instantiation operation. The instantiation process mainly includes several steps:

Note that the modifier of the instance method reload () is public, that is, you can actively call to empty the cache of the implementation class instance of the target loading class and reconstruct the lazyitterer instance. Next, look at the static methods provided by serviceloader:

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
    return new ServiceLoader<>(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> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
        prev = cl;
        cl = cl.getParent();
    }
    return ServiceLoader.load(service,prev);
}

The above three public static methods are all used to construct serviceloader instances. Load (class < s > service, classloader loader) is a typical static factory method. It directly calls the private constructor of serviceloader for instantiation. In addition to specifying the target type of loading class, it also needs to pass in the instance of class loader. Load (class < s > Service) is actually delegated to load (class < s > service, classloader), but the class loader it uses is specified as the thread context class loader. Generally, what the thread context class loader obtains is the application class loader (system class loader). The loadinstalled (class < s > Service) method also sees the shadow of the "parent delegation model". It specifies the class loader as the top-level startup class loader, and finally delegates to load (class < s > service, classloader). Next, we need to focus on serviceloader #iterator ():

public Iterator<S> iterator() {

    //Iterator的匿名实现
    return new Iterator<S>() {
        
    //目标类实现类实例缓存的Map的Entry的迭代器实例
    Iterator<Map.Entry<String,S>> kNownProviders = providers.entrySet().iterator();
        
        //先从缓存中判断是否有下一个实例,否则通过懒加载迭代器LazyIterator去判断是否存在下一个实例
        public boolean hasNext() {
            if (kNownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        //如果缓存中判断是否有下一个实例,如果有则从缓存中的值直接返回
        //否则通过懒加载迭代器LazyIterator获取下一个实例
        public S next() {
            if (kNownProviders.hasNext())
                return kNownProviders.next().getValue();
            return lookupIterator.next();
        }

        //不支持移除操作,直接抛异常
        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

The iterator () is only an anonymous implementation of the iterator interface. The hasnext () and next () methods give priority to judging whether an instance of the implementation class already exists in the cache. If so, it will be returned directly from the cache. Otherwise, it will call the instance of the lazy loading iterator lazyitator to obtain it, and lazyitator itself is also an implementation of the iterator interface, It is a private internal class of serviceloader. The source code is as follows:

private class LazyIteratorimplements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        //加载的资源的URL集合
        Enumeration<URL> configs = null;
        //所有需要加载的实现类的全限定类名的集合
        Iterator<String> pending = null;
        //下一个需要加载的实现类的全限定类名
        String nextName = null;

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

        private boolean hasNextService() {
            //如果下一个需要加载的实现类的全限定类名不为null,则说明资源中存在内容
            if (nextName != null) {
                return true;
            }
            //如果加载的资源的URL集合为null则尝试进行加载
            if (configs == null) {
                try {
                    //资源的名称,Meta-INF/services + '需要加载的类的全限定类名'
                    //这样得到的刚好是需要加载的文件的资源名称
                    String fullName = PREFIX + service.getName();
                    //这里其实ClassLoader实例应该不会为null
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        //从ClassPath加载资源
                        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 S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射构造Class<S>实例
                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 {
                //通过Class#newInstance()进行实例化,并且强制转化为对应的类型的实例
                S p = service.cast(c.newInstance());
                //添加缓存,Key为实现类的全限定类名,Value为实现类的实例
                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 boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action,acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action,acc);
            }
        }

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

    }

Lazyitterer is also the implementation of the iterator interface. Its lazy feature indicates that it will always "lazy judge" or "lazy load" the instance of the next implementation class when the anonymous implementation iterator() of the iterator interface of serviceloader executes hasnext() to judge whether there is a next implementation or next() obtains the instance of the next implementation class. Finally, the source code of the resource file parsing process after loading the resource file:

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);
            }
        }
        //返回的是ArrayList的迭代器实例
        return names.iterator();
}

//解析资源文件中每一行的内容
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) {
            //不能存在空格字符' '和特殊字符'\t'
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service,"Illegal configuration-file Syntax");
            int cp = ln.codePointAt(0);
            //判断第一个char是否一个合法的Java起始标识符
            if (!Character.isJavaIdentifierStart(cp))
                fail(service,"Illegal provider-class name: " + ln);
            //判断所有其他字符串是否属于合法的Java标识符
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service,"Illegal provider-class name: " + ln);
            }
            //如果缓存中不存在加载出来的全类名或者已经加载的列表中不存在加载出来的全类名则添加进去加载的全类名列表中
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

The parsing process of the whole resource file is not complex, mainly including the judgment of character legitimacy of file content and the judgment of cache to avoid repeated loading.

Summary

SPI is widely used in the loading of third-party plug-in class libraries, such as JDBC, JNDI, JCE (java encryption module extension) and so on. Understanding the working principle of serviceloader is helpful to write pluggable class libraries with good scalability.

(end of this article c-1-d e-20181014)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>