Before we discuss Dubbo SPI, let’s take a look at the Java SPI mechanism

What is the Java SPI

Service Provider Interface (SPI) is a Service discovery mechanism. The essence of SPI is to write the fully qualified names of the interface implementation classes in a file, which are loaded by the loader. In this way, the interface can be dynamically replaced with the implementation class at runtime. The SPI mechanism can be extended through the SPI mechanism, which is also used in the framework.

Java spi sample

First define an interface:

public interface Hello {
    void say(a);
}
Copy the code

Add two implementation classes for this interface:

public class HelloImpl1 implements Hello {

    @Override
    public void say(a) {
        System.out.println("I'm implementation class 1."); }}public class HelloImpl2 implements Hello {

    @Override
    public void say(a) {
        System.out.println("I'm implementation class 2."); }}Copy the code

Create folder services under meta-INF of resource and create a file named with the fully qualified name of the interface:

Com. LGX. Study. Spi. Hello file

The contents of the document are as follows:

com.lgx.study.spi.HelloImpl1
com.lgx.study.spi.HelloImpl2
Copy the code

Let’s test it out:

public class Test {

    public static void main(String[] args) {
        ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);
        Iterator<Hello> iterator = loader.iterator();
        while(iterator.hasNext()){ Hello next = iterator.next(); next.say(); }}} I am the implementation class1I'm the implementation class2
Copy the code

We do a simple analysis from the source code:

ServiceLoader loader = ServiceLoader.load(Hello.class); So what does this line of code do?

#ServiceLoader

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 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(a) {
        providers.clear();
        // LazyIterator is an inner class of ServiceLoader that implements the Iterator interface
        lookupIterator = new LazyIterator(service, loader);
}
Copy the code

One simple piece of code you can see is to create a ServiceLoader, pass in the Class loader and interface Class to instantiate, and create an iterator

Iterator Iterator = loader.iterator();

#ServiceLoader
    
public Iterator<S> iterator(a) {
    return new Iterator<S>() {

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

        public boolean hasNext(a) {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next(a) {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove(a) {
            throw newUnsupportedOperationException(); }}; }Copy the code

This line of code creates an iterator to initialize knownProviders

Next we enter the while loop: iterator.hasnext () :

public boolean hasNext(a) {
    if (knownProviders.hasNext())
        return true;
    // Enter this step
    return lookupIterator.hasNext();
}

public boolean hasNext(a) {
    if (acc == null) {
        // Enter here
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run(a) { returnhasNextService(); }};returnAccessController.doPrivileged(action, acc); }}private boolean hasNextService(a) {
    if(nextName ! =null) {
        return true;
    }
    if (configs == null) {
        try {
            // PREFIX is: meta-INF /services
            // service.getName() is com.lgx.study.spi.hello
            // Create a meta-INF /services interface
            // Fully qualified file name
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
            // Load the file from under resource
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x); }}while ((pending == null) | |! pending.hasNext()) {if(! configs.hasMoreElements()) {return false;
        }
        // Come here
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

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; 
            // Do file parsing here and continue
            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();
}

private int parseLine(Class<? > service, URL u, BufferedReader r,int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {...// The following is the specific parsing process
        int ci = ln.indexOf(The '#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if(n ! =0) {
            if ((ln.indexOf(' ') > =0) || (ln.indexOf('\t') > =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);
            }
            / / parse the ln for com. LGX. Study. Spi. HelloImpl1 and com LGX. Study. Spi. HelloImpl2
            // Add the parsed implementation class permission name to the List
            if(! providers.containsKey(ln) && ! names.contains(ln)) names.add(ln); }return lc + 1;
}
Copy the code

After parsing, return the Iterator

Next calls iterator.next() to get the interface:

public S next(a) {
    if (knownProviders.hasNext())
        return knownProviders.next().getValue();
    // Come here and continue down
    return lookupIterator.next();
}

public S next(a) {
    if (acc == null) {
        / / keep going
        returnnextService(); }... }private S nextService(a) {
    if(! hasNextService())throw new NoSuchElementException();
    / / the cn full permissions for the implementation class name: com. LGX. Study. Spi. HelloImpl1 and com. LGX. Study. Spi. HelloImpl2
    String cn = nextName;
    nextName = null; Class<? > c =null;
    try {
        // Load the class through the class loader
        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 {
        // Create an instance of the class
        S p = service.cast(c.newInstance());
        // Add to map cache:
        / / map < com. LGX. Study. Spi. HelloImpl1, HelloImpl1 instance >
        / / map < com. LGX. Study. Spi. HelloImpl2, HelloImpl2 instance >
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}
Copy the code

Now that we’ve seen all the code, it looks pretty simple:

By loading the meta-info /services interface file, parsing each line of the file into the collection, iterating through the collection, getting the permission name of the implementation class, using the class loader to load, finally creating an instance of the class to add to the cache, and finally returning the modified instance.