Implement an RPC framework one by one--Learn java spi

Posted by wrequed on Mon, 15 Jun 2020 03:44:49 +0200

Preface

It's a good idea to understand the java spi(service provider interface) mechanism before going into dubbo source code. Simply put, spi can help us load the interface implementation classes described in the specified file. Hmm... That's it? Isn't that easy, even though I'm a vegetable melon, I know it Class.forName Oops~Let's study it then~

java spi

demo

Despite the uniformity, a working demo is given

// First you need an external interface
public interface GreetOrBye {

    String say(String name);
}

// And two implementation classes
public class Bye implements GreetOrBye {

    @Override
    public String say(String name) {
        return "bye " + name;
    }
}

public class Greet implements GreetOrBye {

    @Override
    public String say(String name) {
        return "hi " + name;
    }
}

//Then the execution class
public class Launcher {

    public static void say(String name) {

        ServiceLoader<GreetOrBye> greetOrByeServiceLoader = ServiceLoader.load(GreetOrBye.class);

        Iterator<GreetOrBye> iterator = greetOrByeServiceLoader.iterator();
        while (iterator.hasNext()) {
            GreetOrBye greetOrBye = iterator.next();
            System.out.println(greetOrBye.say(name));
        }
    }

    public static void main(String[] args) {

        say("wahahah");
    }
}

Following is the file you need to specify, with the directory name fixed to services under META-INF and the file name fully qualified for the interface File content is the fully qualified name of the implementation class Run Results

hi wahahah
bye wahahah

Ordinary ~ (Ancient Music?) Although I haven't seen ServiceLoader's code yet, from my code experience in the 1940s, it must be a process of reading files, reflecting the creation of objects. Okay ~Now prove yourself

ServiceLoader

Watch the notes before the attributes for the vegetable and melon conventions.

Comment Summary

Two nouns are defined in the comment

A service is an exposed interface or class, usually an abstract class or a concrete class, but is not recommended. A service provider is an implementation class, usually a proxy class, whose content determines the specific implementation class Then a few requests were made

**The service provider must provide a parameterless construct** The service provider is defined in META-INF/services, and the file name is the fully qualified name of the service, such asCom.togo.spi.Helloworld.GreetOrByeThe file content is the fully qualified name of the service provider for each line, such asCom.togo.spi.Helloworld.impl.Greet If a service provider appears in more than one file or more times in a single file, the service loader will deweight. Service providers and configuration files do not necessarily need to be in one jar, but service provider s must be accessible by the loader that loads the configuration file (that's what we'll look at then) Service providers are lazy to load (load on demand) ServiceLoader is thread insecure

attribute

ServiceLoader implements the Iterable interface with the following properties.

// File Path
private static final String PREFIX = "META-INF/services/";
// Exposed interface types
private final Class<S> service;
// classloader
private final ClassLoader loader;
// Security-related
private final AccessControlContext acc;
// Cache Loaded Classes
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// Lazy Load Iterator
private LazyIterator lookupIterator;

Looking at the names and comments, you should be able to guess what each attribute does. We'll look at the source code to get a better idea.

Source code

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

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

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

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

The load method actually creates a new ServiceLoader object (because the construction method is private ~), uses the class loader of the current thread, and the entire construction process is some assignment operations. In the reload method, the cached object in the map is cleared and a LazyIterator is re-created, which is all assignment in the LazyIterator construction method. Since it is lazy to load,Of course all the important operations are in use.

public Iterator<S> iterator() {
    return new Iterator<S>() {

        // Loaded Objects
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        // Find from loaded objects first, not lookupIterator
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
        // Logical Icon
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

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

    };
}
public boolean hasNext() {

    if (acc == null) { // By default, the AccessControlContext authors are not concerned about ~~
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try { // Read File Operation
            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;
        }
        // Parse the file, store the string in the file in the list, and return the iterator of the list
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

The next() method is also simpler, reflecting the creation of the object after getting the fully qualified name of the class, just as we started predicting.

summary

This java spi analysis shows that the code is simple, but the focus is on learning thought-oriented programming. For example, we often use different database-driven code and have ServiceLoader in DriverManager.

Topics: Programming Java Attribute Dubbo