SPI and thread context class loader

Collected works: ClassLoader serial burning

premise

Portal: Understand the current class loader, active loading and automatic loading!

demand

The class to be used in the program running process cannot be loaded through the automatic loading of the current class loader (not under the jurisdiction of the class resources of the current class loader). If you want to use this class, you must specify a loader that can load this class to load, and how to obtain this loader is a problem.
Programs are executed in threads, so it is most reasonable to get them from the thread context. Therefore, the thread context class loader is born. This loader is non automatic loading, that is, loading classes through forName or loadClass.

Two scenarios

1. When the high-level provides a unified interface for the low-level to implement, and the low-level classes are loaded (or instantiated) at the high-level, the thread context class loader must be used to help the high-level ClassLoader find and load the classes.
Such as SPI The following will verify from the source code.

2. When the managed class of this class is used for loading, but the classloader (current class loader) that loads this class is unknown, in order to isolate different callers, you can take the thread context class loader of the caller to perform class loading (specify the class loader) instead of hosting.
Like Spring, See how spring is used to load classes in tommcat! Interpreting the use of this scenario

concept

The creator of the thread provides a context ClassLoader for code running in this thread when loading classes and resources. If not set, the default value is the ClassLoader context of the parent thread. Usually, the context ClassLoader of the original thread is set as the class loader used to load the application. By default, it is AppClassLoader;

Get and set

obtain
Thread#getContextClassLoader()
set up
Thread#setContextClassLoader()

Classic usage

The thread Context loader is actually a private data of the thread, which is bound to the thread. After the thread starts the Context component, it will be recycled to the thread pool and then used to do other things. In order not to affect other things, it is necessary to restore the previous thread Context loader.

Usage of thread context class loader (TCCL)

1.Get original TCCL,orign_cl
try{
2.Specify a cl,to TCCL
(ServiceLoader In, use tccl To load the class)
} finally{
3.take TCCL Revert to orign_cl
}

SPI technology and TCCL

Database driven, the official java core library has set the interface, but it has not been implemented, and the three parties have implemented it; Third party implementation classes should be used in the code of single core library
In terms of technical implementation, the ServiceLoader class is loaded by the bootstrap (bs) class, but the bs class loader cannot load the classes implemented by the third party (under the classpath path), so the method cannot be executed. The classes under the classpath path are loaded by AppClassLoader. You can find a way to obtain AppClassLoader at this time. From the perspective of code execution process, threads are actually carrying logical execution, providing a context throughout the whole logic, which can be easily set and obtained in this context.
Of course, the thread context class loader can use other custom CL
Find out how to use ThreadContext ClassLoader from the ServiceLoader source code;
Trace debug code through DriverManager

public class SpiDemo {
    public static void main(String[] args) {
            DriverManager.getConnection("");
}

DriverManager

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

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        ...
    }

ServiceLoader.load(Driver.class);

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();//Get thread context class loader
        return ServiceLoader.load(service, cl);//Incoming cl
    }

new ServiceLoader object, pass in the target class type, and cl

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

If cl is not specified, the system class loader is used
Look at 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();
    }
Construct an iterator and pass in cl
public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

Looking back at the driver manager, load the driver code, get an iterator from the service loader, and traverse

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }

loadedDrivers.iterato(); The returned is Java util. 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();
            }

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

        };
    }

Looking at the structure, both the hasNext() and next() methods call the hasNext and next methods of the lookupIterator. Then continue to look at the two methods of the lookupIterator,

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

The key point is the two methods of hasNextService () and nextService ()

First look at the nextService() method. In the code,

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();          // This cannot happen
        }

The key point is in class forName(cn,false,loader); The last parameter loader is the thread context class loader passed in above, so you can know how the so-called SPI uses the thread context class loader to load classes here; Then find out why the thread class loader and how to break the parental delegation mechanism for class loading;
Simple summary; SPI breaks the two parent delegation mechanism to load classes by specifying the class loader, which is carried (assigned and taken out) by the thread context class loader

hasNextService() method

private boolean hasNextService() {
            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;
        }

Because private static final String PREFIX = "META-INF/services /";
So fullname: meta-inf / services / Java sql. Driver∂
configs = loader.getResources(fullName); After loading the resource, click
pending = parse(service, configs.nextElement()); Parse the resource and obtain the pending result
0 = "com.mysql.jdbc.Driver"
1 = "com.mysql.fabric.jdbc.FabricMySQLDriver"

Truly understand thread context class loader (multi case analysis)

Java SPI details

SPI mechanism in Java that must be understood by advanced development
ClassLoader, the most mysterious technology in Java, is enough to read this article
Out of the maze of class loaders



Author: rock_fish
Link: https://www.jianshu.com/p/304cb533ba2d
Source: Jianshu
The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.

Keywords: Java

Added by jack_ on Sun, 23 Jan 2022 10:45:45 +0200