Starting today, DUbbo will be introduced step by step. First, a brief overview of DUbbo.

An overview of the

Dubbo is the core framework for SOA(Service-oriented Architecture) service governance solutions. For distributed invocation, the emphasis is on distributed governance. In a nutshell, it can be divided into four roles. Providers, consumers, registries and monitoring centers. Services are registered and subscribed to through a registry and monitored through a monitoring center. * Core features *

  • Remoting: Provides abstract encapsulation of various NIO frameworks, including synchronous to asynchronous and request-response information exchange modes.
  • Cluster: Service framework that provides transparent remote procedure calls based on interface methods, including multi-protocol support, and Cluster support for soft load balancing, failure tolerance, address routing, and dynamic configuration.
  • Registry: service registration, based on the Registry directory service, so that service consumers can dynamically find service providers, so that addresses are transparent, so that service providers can smoothly add or subtract machines.

*Dubbo component role *

Provider: service Provider that exposes the service Consumer: service Consumer that invokes the remote service Registry: Registry for service registration and discovery Monitor: monitoring center that collects statistics on service invocation times and invocation time Container: The service runs the container, a common one being the Spring container

Call relationship:

  1. The service container is responsible for starting, loading, and running the service provider
  2. At startup, service providers register their services with the registry.
  3. At startup, service consumers subscribe to the registry for the services they need.
  4. The registry returns a list of service provider addresses to the consumer, and if there are changes, the registry pushes the change data to the consumer based on the long connection.
  5. The service consumer, from the provider address list, selects one provider to call based on the soft load balancing algorithm. If the call fails, selects another one to call.
  6. Service consumers and providers accumulate call times and call time in memory, and regularly send statistical data to Monitor every minute.

SPI(Service Provider Interfaces)

It is a set of apis provided by Java that can be implemented or extended by third parties to enable framework extensions and replace components. In the JDK documentation, it explains this:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.

In object-oriented design, it is recommended to program modules based on interfaces, rather than hard-coding implementation classes, for pluggability of module design. A mechanism for service discovery is needed in order for modules to be assembled without specifying which implementation they are. The jDK’s SPI is to find a service implementation for an interface.

The Java SPI is essentially a dynamic loading mechanism implemented by a combination of interface-based programming + policy pattern + configuration files. It looks for a service implementation mechanism for an interface. It is similar to the idea of IOC, which is to move the control of assembly out of the program. This mechanism is especially important in modular design, so its core idea is decoupling

Usage scenarios

  • JDBC loads drivers for different types of databases
  • SLF4J loads logging implementation classes from different vendors
  • Spring
  • Dubbo

Directions for use

  1. When the service provider provides a concrete implementation of the interface, create a file named “fully qualified name of the interface” in the META-INF/service directory of the JAR package, containing the fully qualified name of the implementation class.
  2. The JAR package that implements the interface class is placed in the classpath of the main program
  3. The main program loads the implementation template dynamically through java.util.Serviceloader, which loads the class into the JVM by scanning the meta-INF /services directory for the fully qualified name of the implementation class
  4. The IMPLEMENTATION class of SPI must carry a constructor that takes no arguments
public final class ServiceLoader<S> implements 可迭代<S>
{

    private static final String PREFIX = "META-INF/services/";

    // Represents the loaded class or interface
    private final Class<S> service;
    // Class loaders used to locate, load, and instantiate providers
    private final ClassLoader loader;
    // Access control context used when creating the ServiceLoader
    private final AccessControlContext acc;
    // Cache providers in the order they are instantiated
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // lazy search iterator
    private LazyIterator lookupIterator;

    // The ServiceLoader is re-created for the new service provider to install into the running Java VIRTUAL machine
    public void reload(a) {
        // Clear the cache of all instantiated service providers
        providers.clear();
        // Create a new iterator that will find and instantiate the service provider from scratch.
        lookupIterator = new LazyIterator(service, loader);
    }

    /** ** Private constructor ** creates a service loader with the specified class loader and service ** If no class loader is specified, use the system class loader, which is the application class 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();
    }

   // Parse the failure handling method
    private static void fail(Class
        service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ":" + msg,
                                            cause);
    }

    private static void fail(Class
        service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ":" + msg);
    }

    private static void fail(Class<? > service, URL u,int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ":" + msg);
    }

    // Parse a line in the service provider configuration file
    // First uncomment validation, then save
    // Return the next line number
    // Duplicate configuration items are not saved
    private int parseLine(Class<? > service, URL u, BufferedReader r,int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        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);
            }
            if(! providers.containsKey(ln) && ! names.contains(ln)) names.add(ln); }return lc + 1;
    }

  // Parses the configuration file to parse the specified URL configuration file
  // Uninstantiated service providers are saved in the cache using the parseLine method for parsing.
    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();
    }

    // The iterator that the service provider looks up
    private class LazyIterator implements Iterator<S>
    {
        // Service provider interface
        Class<S> service;
        // Class loader
        ClassLoader loader;
        // Save the url of the implementation class
        Enumeration<URL> configs = null;
        // Save the full name of the implementation class
        Iterator<String> pending = null;
        // The full name of the next implementation class in the iterator
        String nextName = null;

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

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

        private S nextService(a) {
            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
        }

        public boolean hasNext(a) {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run(a) { returnhasNextService(); }};returnAccessController.doPrivileged(action, acc); }}public S next(a) {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run(a) { returnnextService(); }};returnAccessController.doPrivileged(action, acc); }}public void remove(a) {
            throw newUnsupportedOperationException(); }}// Get the iterator
    // Returns an iterator that iterates through the service provider
    // Load available service providers lazily
    // The lazy implementation is that parsing the configuration file and instantiating the service provider is done by the iterator itself
    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(); }}; }// Create a ServiceLoader for the specified service using the specified class loader
    public static <S> ServiceLoader<S> load(Class service, ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

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

   // Create a ServiceLoader for the specified service using the extended class loader
   // Only service providers already installed in the current Java VIRTUAL machine can be found and loaded. Service providers in the application classpath are ignored
    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);
    }

    /**
     * Returns a string describing this service.
     *
     * @return  A descriptive string
     */
    public String toString(a) {
        return "java.util.ServiceLoader[" + service.getName() + "]"; }}Copy the code

ServiceLoader is not a concrete implementation that reads files after instantiation. Instead, the configuration files are loaded and parsed when iterated, loaded and parsed when called to hasNext, and instantiated and cached when called to Next.

Advantages The advantage of using the Java SPI mechanism is decoupling, so that the assembly control logic of a third-party service module is separated from the caller’s business code, rather than coupled together. Applications can enable framework extensions or replace framework components depending on the business situation.

Disadvantages Although ServiceLoader is lazy-loaded, it can only be obtained by traversing the interface, i.e. the implementation classes are loaded and instantiated once. If you don’t want to use some implementation class, it gets loaded and instantiated, which is wasteful. The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter. It is not safe for multiple concurrent threads to use instances of the ServiceLoader class.

Dubbo’s SPI mechanism

As can be seen from the figure, when Dubbo extends each module, it is associated with extension points through ExtensionLoader. Extension points in Dubbo need to meet the following characteristics:

  1. The extension point must be of type Interface and must be annotated by @spi
  2. The configuration files are stored in meta-INF /services/ and meta-INF /dubbo/ and meta-INF /dubbo/internal. The files defined in these paths are the full class names of the extension point interfaces. The files are configured as key-value pairs for the extension implementation of the extension point. This is quite different from the STORAGE form of the JDk SPI, so instead of using ServiceLoader directly in Dubbo, you use ExtensionLoader, which can be used to load various configurable components in Dubbo. For example, ProxyFactory, LoadBalance, RCP Protocol, Filter, Container, Cluster and registry type. In META-INF/dubbo/internal/com.alibaba.dubbo.com, mon. The extension. ExtensionFactory extension defined in:
adaptive = com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory 
spi = com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory 
spring = com.alibaba.dubbo.config.spring.extension.SpringExtensionFactor
Copy the code

These are used to identify extension points, @spi, @Adaptive, and @activate

@spi (annotation on class) : This annotation identifies the interface as an extension point, and the value attribute specifies the name of the default adaptation extension point. @activate (annotated on type and method) : the @activate annotation on the implementation class of the extension point indicates that the extension class can be obtained if the condition is met, and the extension class cannot be obtained if the condition is not met. It is filtered by the group and value attributes of the @activate annotation. Adaptive(annotation on type and method) : If the annotation is on a class, this class is the default Adaptive extension. When annotating the extension Interface method, Dubbo will dynamically generate an Adaptive extension Class (generating code, dynamically compiling the instantiation Class) named the simple extension Interface name +$Adaptive. The purpose of this method is to accommodate different extension instances at runtime. At runtime, the name of the extension class to be used is obtained from the URL through the parameter of the URL type passed in or the parameter of the internal method of obtaining the URL, and then the corresponding extension instance is loaded according to the name, and the same method is called with the extension instance object. If the runtime does not match the running extension instance, the @SPI annotation is used to specify the default extension. In this way, the runtime ADAPTS to the corresponding extension. Let’s pick a random interface defined in the source code: Transporter

@SPI("netty")
public interface Transporter {
    // Bind a server
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    // To connect to a server, create a client
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
Copy the code

ExtensionLoader will pass createAdaptiveExtensionClassCode method that dynamically generate a class from the $the Adaptive, the generated code is as follows:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        // If the URL argument is empty, an exception is thrown.
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter"."netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter"."netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        returnextension.bind(arg0, arg1); }}Copy the code

This code is template code, the core of which is a single line to get the extended instance object with the specified name. com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class) .getExtension(extName);

ExtensionLoader

It controls the initialization and loading of all extension points. Two static properties are stored in ExtensionLoader. EXTENSION_LOADERS holds the instance object of ExtensionLoader corresponding to the kernel open extension point. EXTENSION_INSTANCES hold extension types (classes) and instance objects of extension types

private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

    // This is the configuration file path in the JDK's SPI extension mechanism, dubbo for compatibility with JDK's SPI
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    // Used to save the user-defined extension implementation configuration file path
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    // The path to the extended implementation configuration file provided by Dubbo
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    // Set of extension loaders. Key is the extension interface, such as Protocol
    private static finalConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS =newConcurrentHashMap<Class<? >, ExtensionLoader<? > > ();// Extension implementation set, key for extension implementation class, value for extension object
    // For example, key is Class
      
        and value is the DubboProtocol object
      
    private static finalConcurrentMap<Class<? >, Object> EXTENSION_INSTANCES =newConcurrentHashMap<Class<? >, Object>();// Extend the interface, such as Protocol
    private finalClass<? > type;// Object factory, which gets an instance of the extension implementation for injectExtension method to inject instances of the extension implementation class into the associated dependency properties.
    // If the StubProxyFactoryWrapper class has the Protocol Protocol property, the Protocol implementation instance is assigned by the set method
    private final ExtensionFactory objectFactory;

    // The extensions mentioned below are key values in the configuration file, such as "dubbo", etc

    // Cache extensions are mapped to extended classes, swapping keys and values for cachedClasses.
    private finalConcurrentMap<Class<? >, String> cachedNames =newConcurrentHashMap<Class<? >, String>();// A collection of extended implementation classes for caching
    private finalHolder<Map<String, Class<? >>> cachedClasses =newHolder<Map<String, Class<? > > > ();// The extension is mapped to the automatic activation class with @activate
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    // A collection of cached extension objects. Key is the extension name and value is the extension object
    // For the Protocol extension, key is dubbo and value is DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    // Cache Adaptive extension objects, such as those of the AdaptiveExtensionFactory class
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    // The cached adaptive extension object class, such as the AdaptiveExtensionFactory class
    private volatileClass<? > cachedAdaptiveClass =null;

    // The default extension of the cache is the value set in @spi
    private String cachedDefaultName;

    // Create cachedAdaptiveInstance
    private volatile Throwable createAdaptiveInstanceError;

    // Extend the Wrapper implementation class collection
    privateSet<Class<? >> cachedWrapperClasses;// The mapping between the extension name and the exception that is loaded for the extension class
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
Copy the code

ExtensionLoader does not provide a public constructor. There is a private constructor that takes the factory method of the ExtensionLoader instance, but provides a public static getExtensionLoader. The members of the public methods: there are three important methods in getActiveExtension: according to the condition for current extension is implemented automatically activated getExtension: according to the names for the current expansion of the specified implementation getAdaptiveExtension: Gets an adaptive implementation of the current extension

 private ExtensionLoader(Class
        type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
Copy the code
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);

}
Copy the code

As can be seen above, ExtensionFactory is also an extension point with two implementation classes: SpiExtensionFactory and AdaptiveExtensionFactory, and actually a SpringExtensionFactory, different implementation classes can load extension point implementations in different ways. If the extension point type to be loaded is ExtensionFactory, object is set to NULL. In the default ExtensionFactory implementation, AdaptiveExtensionFactory is annotated by the @Adaptive annotation, that is, it is the Adaptive extension implementation corresponding to ExtensionFactory (at most one Adaptive implementation per extension point, If none of the implementations are annotated by @Adaptive, dubbo dynamically generates an Adaptive implementation class.)

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    // A collection of extension objects, the default can be divided into dubbo's SPI interface implementation class objects or Spring bean objects
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory(a) {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        // Iterate over all supported extensions
        for (String name : loader.getSupportedExtensions()) {
            // Add objects to the collection
            list.add(loader.getExtension(name));
        }
        // Returns an unmodifiable collection
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            // Get the extension object through the extension interface and extension name
            T extension = factory.getExtension(type, name);
            if(extension ! =null) {
                returnextension; }}return null; }}Copy the code

The getSupportedExtensions method of the ExtensionLoader class is called in the code above, so analyze the ExtensionLoader class.

 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        // The extension point interface is empty, throwing an exception
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        // Check whether type is an interface class
        if(! type.isInterface()) {throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // Determine whether the interface is extensible
        if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        // Retrieve the extension loader corresponding to the extension interface from the extension loader collection
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        // If empty, create an extension loader for the extension interface and add it to EXTENSION_LOADERS
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
Copy the code
public T getAdaptiveExtension(a) {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // Create an adapter object
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: "+ t.toString(), t); }}}}else {
                throw new IllegalStateException("fail to create adaptive instance: "+ createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); }}return (T) instance;
    }
Copy the code

As you can see in the ExtensionLoader’s private constructor, we are not using getExtension(name) to get a specific implementation class when we select ExtensionFactory. Instead, call getAdaptiveExtension to get an adaptive implementation. First check if the cached adaptiveInstance exists and use it if it does, otherwise call createAdaptiveExtension to create a new adaptiveInstance and cache it. Each call ExtensionLoader. GetAdaptiveExtension get are the same instance. First call in call getAdaptiveExtensionClass getExtensionClasses in getAdaptiveExtensionClass () (), call getExtensionClasses () to obtain the extension implementation class array, Put it in the cachedClasses property. When cachedClasses is empty, calling loadExtensionClasses() getExtensionClasses() loads all implementations of the current Extension. If @adaptive exists, the value is assigned to the cachedAdaptiveClass property and cached. If no @Adaptive implementation is found, an AdaptiveExtensionClass is dynamically created.

We first get the value in the annotation of the extension point class, get the default value, then read the information in the configuration file from the specific directory, and finally put the relevant class into the extensionClasses variable via loadClass

 private T createAdaptiveExtension(a) {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: "+ e.getMessage(), e); }}privateClass<? > getAdaptiveExtensionClass() { getExtensionClasses();// Cache adaptive extension object
        if(cachedAdaptiveClass ! =null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
privateMap<String, Class<? >> getExtensionClasses() { Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
    }


privateMap<String, Class<? >> loadExtensionClasses() {final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation ! =null) {
            // the default value in @spi
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                // Only one default value is allowed
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ":" + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0]; }}// Load the implementation class array from the configuration fileMap<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;
    }


private void loadDirectory(Map
       
        > extensionClasses, String dir)
       ,> {
        // Concatenate the fully qualified name of the interface to get the complete filename
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            // Get ExtensionLoader class information
            ClassLoader classLoader = findClassLoader();
            if(classLoader ! =null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if(urls ! =null) {
                // Walk through the file
                while(urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); }}}catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t); }}private void loadResource(Map
       
        > extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)
       ,> {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                while((line = reader.readLine()) ! =null) {
                    // Skip the content annotated by #
                    final int ci = line.indexOf(The '#');
                    if (ci >= 0) line = line.substring(0, ci);
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                // Split key and value according to "="
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // Load the extension class
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); }}catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: "+ t.getMessage(), t); exceptions.put(line, e); }}}}finally{ reader.close(); }}catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in "+ resourceURL, t); }}private void loadClass(Map
       
        > extensionClasses, java.net.URL resourceURL, Class
         clazz, String name)
       ,> throws NoSuchMethodException {
        // Whether the class implements an extension interface
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        // Determine whether the class is an adapter for the extended interface
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if(! cachedAdaptiveClass.equals(clazz)) {throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ","+ clazz.getClass().getName()); }}else if(isWrapperClass(clazz)) { Set<Class<? >> wrappers = cachedWrapperClasses;if (wrappers == null) {
                cachedWrapperClasses = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); }else {
            // Get the constructor object by reflection
            clazz.getConstructor();
            // No extension is configured. The extension is automatically generated. For example, DemoFilter is demo, which is mainly used for compatible Java SPI configuration.
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config "+ resourceURL); }}// Get the extension, which can be an array, with multiple extension extensions.
            String[] names = NAME_SEPARATOR.split(name);
            if(names ! =null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                // If it is an automatically activated implementation class, it is added to the cache
                if(activate ! =null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if(! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); }// Cache extension implementation classClass<? > c = extensionClasses.get(n);if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if(c ! = clazz) {throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

Copy the code

The code above completes the implementation and instantiation of the adaptive extension point type. The following method is an implementation of automatic extension point injection. It obtains the parameter types and property names of all set methods that process the current instance. Then do the injection.

private T injectExtension(T instance) {
        try {
            if(objectFactory ! =null) {
                // Reflection gets all the methods in that class
                for (Method method : instance.getClass().getMethods()) {
                    // If set is used
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if(method.getAnnotation(DisableInject.class) ! =null) {
                            continue; } Class<? > pt = method.getParameterTypes()[0];
                        try {
                            The StubProxyFactoryWrapper class has the Protocol Protocol property,
                            String property = method.getName().length() > 3 ? method.getName().substring(3.4).toLowerCase() + method.getName().substring(4) : "";
                            // Get a property value, such as a Protocol object, or possibly a Bean object
                            Object object = objectFactory.getExtension(pt, property);
                            if(object ! =null) {
                                // Inject dependency propertiesmethod.invoke(instance, object); }}catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ":" + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

Copy the code

Dubbo ExtensionLoader [Dubbo] Adaptive