This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details

What is Dubbo SPI

At the heart of the Java SPI and Dubbo SPI is one: the ** policy pattern. ** Use different implementations for different situations.

Dubbo SPI function

In addition to implementing the Java SPI, Dubbo SPI adds some new features.

  • Automatic extension Point Wrapping (AOP)
  • Extension Point Automatic Assembly (IOC)
  • Extension point adaptation
  • Extension points are automatically activated

The source code to read

Dubbo SPI

How to use

  1. Define interfaces and write implementation classes
// Define the interface,
@SPI // The @spi annotation identifies this interface as an interface that can be extended by SPI
public interface HelloService {
    String sayHello(a);
}


// Define two implementation classes, one Chinese implementation and one English implementation
public class ChinaeseHelloService implements HelloService {
    @Override
    public String sayHello(a) {
        return "Hello"; }}public class EngishHelloService implements HelloService {
    @Override
    public String sayHello(a) {
        return "Hello"; }}Copy the code
  1. Define the key corresponding to the implementation class so that dubbo SPI can find it

Dubbo SPI specifies the different implementations of the interface in a file and a key identifier for each implementation. It is convenient to specify the concrete implementation at use. The name and location of the file needs to accord with SPI standard of the definition of dubbo location: the resources/meta-inf/dubbo directory file name: the full name of the class of the interface, such as org. Apache. Dubbo. Study. SPI. HelloService file contents: The key of the implementation class = the full class name of the implementation class, e.g

china=org.apache.dubbo.study.spi.ChinaeseHelloService
english=org.apache.dubbo.study.spi.EngishHelloService
Copy the code
  1. Using different implementations
public void spiTest(a) {
        // Get the China implementation
        HelloService chinaHelloService = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

        // Get the English implementation
        HelloService englishHelloService = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension("english");
        System.out.println(englishHelloService.sayHello());

    }
Copy the code

The source code to explore

According to the above function, you can see the source code entry is in ExtensionLoader getExtensionLoader (), the source must be with a purpose to see, such as Dubbo SPI source mainly, how to implement class loading configuration files, how to get the source code of the object.

  1. Get ExtensionLoader
HelloService chinaHelloService = ExtensionLoader
                // Get the ExtensionLoader entry
                .getExtensionLoader(HelloService.class)
                // Get the concrete implementation class according to name
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if(! type.isInterface()) {throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        // Get ExtensionLoader object from the cache. Note that the cache key is the Class of the interface
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {// The cache was not created
            // Look at the following logic for new ExtensionLoader
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

/ / create ExtensionLaoder
private ExtensionLoader(Class
        type) {
        this.type = type;
        // ExtensionFactory is also loaded via SPI, adaptive mode, this can be skipped for now, just know
        // ExtensionFactory is used to create ExtensionLoader objects
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
Copy the code

2. Obtain the specific implementation according to name

HelloService chinaHelloService = ExtensionLoader
                // Get the ExtensionLoader entry
                .getExtensionLoader(HelloService.class)
                // Get the implementation-class entry by name
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

// Enter the getExtension(" China ") method, which has some code for caching objects
public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // Create the Holder from the cache.
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();

        // If the implementation class is not instantiated, it is instantiated and put into the holder
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) { instance = createExtension(name, wrap); holder.set(instance); }}}return (T) instance;
    }

// getOrCreateHolder code
private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
Copy the code

Let’s draw a getExtension flow chart



If the Holder does not exist in the cache, create it and then set it to the cache.

For the sake of writing this article, give the above operation a name,”getOrCreateAndSet“.

Coming back to the question, why encapsulate another layer of Holder?

This is an optimization for lock granularity. Normally, if I were to write getOrCreateAndSet to get an Object, I would do a Map

and double check, And then synchronized (Map < String, Object >). In this case, the entire cached Map will be locked, which will block other names as well, which is not necessary because we only need to lock the same name. After name obtains the corresponding Holder, the lock target can be replaced with Holder object, reducing the granularity of the lock. Going back to the question at the beginning and looking at how the object is created, the entry method is createExtension.
,object>

private T createExtension(String name, boolean wrap) {
        // Get the Class object whose name corresponds to the implementation Class, loaded from the configuration fileClass<? > clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {// reflection creates the implementation class
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            / /... Part of the code is omitted
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: "+ t.getMessage(), t); }}Copy the code

It is obvious from the code that the object is instantiated by reflection. Go to getExtensionClasses to find the code for loading the configuration file.

privateMap<String, Class<? >> getExtensionClasses() { Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();// Load the configurationcachedClasses.set(classes); }}}return classes;
    }
Copy the code

Enter the loadExtensionClasses

privateMap<String, Class<? >> loadExtensionClasses() {// Cache default implementation name, @spi (" default name ")cacheDefaultExtensionName(); Map<String, Class<? >> extensionClasses =new HashMap<>();
        // LoadingStrategy is loaded via Java SPI
        // 1. Load the META-INF/dubbo/internal/ SPI extension
        // 2. Load the SPI extension for meta-INF /dubbo/
        // 3. Load the SPI extension for meta-INF /services/
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache"."com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }
Copy the code

View the loadDirectory procedure

private void loadDirectory(Map<String, Class<? >> extensionClasses, String dir, String type,boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
        / / access interface configuration file name, such as "meta-inf/dubbo/internal/com. The study. The service. The HelloService." "
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            // Find files under Resources using ClassLoader
            // Different class loaders can load different range of resource files. For example, AppClassLoader can load only
            // Resource files in the project and third-party jar cannot be loaded in rt.jar because rt.jar
            // The package is loaded with BootStrapClassLaoder
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }

            if (urls == null| |! urls.hasMoreElements()) {if(classLoader ! =null) {
                    urls = classLoader.getResources(fileName);
                } else{ urls = ClassLoader.getSystemResources(fileName); }}if(urls ! =null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // If the same interface obtains multiple configuration files, the interface will merge themloadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages); }}}catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
Copy the code

After obtaining the configuration file of the interface, the next step is to read the configuration file for parsing and enter the loadResource method

private void loadResource(Map<String, Class<? >> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL,boolean overridden, String... excludedPackages) {
        try {
            // Read the IO stream
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                String clazz = null;
                while((line = reader.readLine()) ! =null) {
                    final int ci = line.indexOf(The '#'); // # indicates a comment
                    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) {
                                name = line.substring(0, i).trim(); / / get the key
                                clazz = line.substring(i + 1).trim(); // Get the implementation class full thunder
                            } else {
                                clazz = line;
                            }
                            if(StringUtils.isNotEmpty(clazz) && ! isExcluded(clazz, excludedPackages)) { loadClass(extensionClasses, resourceURL, Class.forName(clazz,true, classLoader), name, overridden); }}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);
                        }
                    }
                }
            }
        }
    }
Copy the code

The IO stream is read directly, then the key and the class are parsed, and then loaded into the loadClass method

private void loadClass(Map<String, Class<? >> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) throws NoSuchMethodException {
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);// @extension (" Implementation class name ")
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);// Load the loaded implementation class into the cache}}}}Copy the code

Above is read configuration, reflection instantiation of normal Dubbo SPI extension source code.

Extension point automatic assembly

IOC, we find the answer to the following questions from the source code:

  1. How do I find object instances to inject
  2. What is the injection method (contrast setter, constructor, annotation injection in Spring)
  3. How do I deal with circular dependencies
private T injectExtension(T instance) {
        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if(! isSetter(method)) {// Find setter methods
                    continue;
                }
                /**
                 * 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];// Get the Class type of the value of set
                if (ReflectUtils.isPrimitives(pt)) {// Whether the parameter type is a basic data type (eight data types + corresponding packaging types, these parameters do not need IOC)
                    continue;
                }

                try {
                    String property = getSetterProperty(method);// Get the property name of the setter method
                    Object object = objectFactory.getExtension(pt, property);// Get the injected object from the SPI cache
                    if(object ! =null) {
                        method.invoke(instance, object);// Call setter methods for injection}}catch (Exception e) {
                    logger.error("Failed 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

Through the above code, you can see that the injected object instance is generated by ExtensionLoader, circular dependencies problem also need to enter the objectFactory getExtension method view, not click method one by one, lists the key code directly

private T createAdaptiveExtension(a) {
        try {
            // For IOC injection, the setter value is also created by reflection and the injectExtension injection property is called
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: "+ e.getMessage(), e); }}Copy the code

This indicates that IOC implemented by Dubbo SPI does not deal with cyclic dependencies. It indicates that the current IOC use of Dubbo is also relatively simple, without cyclic dependencies. However, it can be expected that if cyclic dependencies occur, there will be an infinite loop. I forgot to mention that IOC must annotate at least one of the classes in which the values of setter methods are injected with ** @adaptive **. This part of the code extends the Adaptive specification. Enter getAdaptiveExtensionClass ()

Extension point automatic wrapping

Automatic wrapping is AOP, and each wrapper class is named XXXXXXWrapper. Find the problem from the source:

  1. How to identify the original object that needs to be wrapped
  2. There are JDK dynamic proxies, AMS generated bytecode proxies, and static proxies. What does Dubbo SPI do?
  3. How is the order of agents set
private T createExtension(String name, boolean wrap) {
        // Get the Class object whose name corresponds to the implementation Class, loaded from the configuration fileClass<? > clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {// reflection creates the implementation class
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            / / the ioc injection
            injectExtension(instance);

            / / aop agent
            if(wrap) { List<Class<? >> wrapperClassesList =new ArrayList<>();
                if(cachedWrapperClasses ! =null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);// When classes are loaded from the configuration file, proxy classes are loaded into the cachedWrapperClasses list
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);// Proxy ascending sort
                    Collections.reverse(wrapperClassesList);/ / reverse
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for(Class<? > wrapperClass : wrapperClassesList) { Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);if (wrapper == null|| (ArrayUtils.contains(wrapper.matches(), name) && ! ArrayUtils.contains(wrapper.mismatches(), name))) {// If the order is small, it will be executed firstinstance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}}}// Handle initialization
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: "+ t.getMessage(), t); }}Copy the code

Load the corresponding agent

private void loadClass(Map<String, Class<? >> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) throws NoSuchMethodException {
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);// AOP loads the Wrapper proxy corresponding to Clazz
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);// @extension (" Implementation class name ")
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);// Load the loaded implementation class into the cache}}}}Copy the code

Extension point adaptation

When you actually use the Dubbo SPI extension, instead of hardcoding getExtension(” China “) directly, you get it dynamically, as in

String extensionName = "";// Can be delivered via RPC, HTTP, or the registry
        HelloService service = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension(extensionName);
        System.out.println(englishHelloService.sayHello());
Copy the code

In Dubbo, the name of the extension is placed in the unified URL including the registry opening each time an RPC call is made.

URL

URL is not the default URL. It is called uniform Resource Locator (URL). It is used to uniquely locate a resource on the Internet.

[protocol]://[username]:[password]@ip:port/[path]? [a=1&b=2]

// Username: password @ip:port/ resource path? Additional parameters, such as the following example, can be represented as a URL

  • 192.168.1.2:3306 contains only IP addresses and ports
  • www.baidu.com is transmitted through HTTP

In Dubbo, URL is cleverly used as the data structure for transmitting metadata. When using URL, it only needs simple concatenation string or partition string, and there is no need to use complex and inefficient serialization method to transmit metadata. Such as:

  • In the use of the registry scenario, use a zookeeper as registry zookeeper: / / 192.168.2.0:22
  • In the scenario of using a registry, use redis as the registry redis://192.168.5.4
  • In the exposed service scenario, the use of dubbo agreement dubbo exposed service: / / 192.168.5.4:25556 / com. Service. HelloService
  • GRPC protocols are used in exposed service scenario, the exposed service GRPC: / / 192.168.22.4:232 / com. Service. HelloService
  • In reference service scenario, the use of dubbo protocol reference service dubbo: / / 192.168.5.4:25556 / com. Service. HelloService

It can be seen that in the above example, it is specially emphasized in the XXXX scenario because some urls may be duplicated. For example, the exposed service and the reference service both use the Dubbo protocol, so the URLS are the same. Therefore, when analyzing the meanings of URL parameters, you need to consider the current scenario.

The URL class model in Dubbo is defined as follows:

class URL implements Serializable {
    protected String protocol;
    protected String username;
    protected String password;

    // by default, host to registry
    protected String host;

    // by default, port to registry
    protected int port;

    protected String path;

    private final Map<String, String> parameters;
}
Copy the code

For example, HelloService, which is defined at the beginning of the interface, has a sayHello method with a URL as its input. The @adaptive (“hello”) annotation is used to indicate that the method is Adaptive. The @spi annotation on a class indicates that the interface is an extension interface. We also defined two implementations at the beginning, one in Chinese and one in English, and configured them in SPI files.

@SPI
public interface HelloService{
    @Adaptive("hello")
    String sayHello(URL url);
}
Copy the code

When an implementation class is retrieved using SPI and a method annotated with Adaptive is called, the corresponding implementation class is loaded for execution based on the argument corresponding to “hello” in the URL.

HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class)
                .getAdaptiveExtension();
        URLBuilder urlBuilder = new URLBuilder();
        urlBuilder.addParameter("hello"."china");// Specify the China implementation class
        URL url = urlBuilder.build();
        System.out.println(adaptiveExtension.sayHello(url));
Copy the code

The principle is also very simple. When getAdaptiveExtension is used to obtain the Adaptive extension, the method with @Adaptive annotation will be proxy. The name of the corresponding implementation class will be obtained from the URL parameter according to the key specified in the annotation, and then the corresponding method will be executed after loading the implementation class using SPI. In Dubbo, the proxy method is implemented by generating classes. Here is the core generation method body code.

private String generateMethodContent(Method method) {
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            return generateUnsupported(method);
        } else {// There are Adaptive annotations
            int urlTypeIndex = getUrlTypeIndex(method);

            // found parameter in URL type
            if(urlTypeIndex ! = -1) {
                // Check whether the method has a URL entry
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));// Check whether there is a method to get the Url from the method argument
            }
            // Annotate the specified key. The annotation key can specify multiple values. All values are array types
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            // Whether the method argument is of the Invocation type
            boolean hasInvocation = hasInvocationArgument(method);
            // Generates the code to get the Invocation method name
            code.append(generateInvocationArgumentNullCheck(method));
            // Generate a method to obtain the corresponding value from the URL by specifying the key via an annotation. The value will be stored in the extName variable
            code.append(generateExtNameAssignment(value, hasInvocation));
            // Generate code to determine whether extName is null
            code.append(generateExtNameNullCheck(value));
            // Generate code to get extended objects via SPI and extName
            code.append(generateExtensionAssignment());

            // Generate the code to execute the extension object
            code.append(generateReturnAndInvocation(method));
        }

        return code.toString();
    }
Copy the code

The logic of generating the code looks pretty daunting, but this approach has the advantage of higher performance without reflection. The generated code can be viewed through breakpoints:

import org.apache.dubbo.common.extension.ExtensionLoader;

public class HelloService$Adaptive implements org.apache.dubbo.study.spi.HelloService {
    public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("hello");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.study.spi.HelloService) name from url (" + url.toString() + ") use keys([hello])");
        org.apache.dubbo.study.spi.HelloService extension = (org.apache.dubbo.study.spi.HelloService) ExtensionLoader.getExtensionLoader(org.apache.dubbo.study.spi.HelloService.class).getExtension(extName);
        returnextension.sayHello(arg0); }}Copy the code

The above code can see that the proxy calls the implementation class’s methods directly, without a call like **method.invoke()** reflection, which gives better performance.

Extension points are automatically activated

Extension point auto-activation is used in Dubbo’s collection class extension implementation, such as Filter. If a Filter implementation class is annotated with @activate (” XXX”) and “XXX” is specified in the URL, the corresponding implementation class is automatically activated. Through ExtensionLoader. GetActivateExtension method can obtain a list of all to activate the implementation class.

// Some non-core code was removed. Note that names are usually null if passed as a whitelist
public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        // Names is Empty in most cases
        if(! names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { getExtensionClasses();for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                }  else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup)// Check whether the group matches
                        && !names.contains(name) //
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) { // Whether the value on the annotation is included in the URLactivateExtensionsMap.put(getExtensionClass(name), getExtension(name)); }}if(!activateExtensionsMap.isEmpty()){
                activateExtensions.addAll(activateExtensionsMap.values());
            }
        }
        return activateExtensions;
    }
Copy the code

conclusion

All of the extensions in Dubbo are designed around URL+SPI+IOC+AOP, and although the code is very complex, it works very well, which is something to keep in mind when designing the framework, no matter how complex, abstract, or strange the technical implementation. Keep your API design simple, easy to use, and easy to use.