Apache Dubbo is a microservices development framework that provides two key capabilities: RPC communication and microservices governance. This means that the microservices developed by Dubbo will have the ability of remote discovery and communication between each other, and the rich service governance capabilities provided by Dubbo can realize service governance demands such as service discovery, load balancing, and traffic scheduling. Dubbo is also highly extensible, allowing users to customize their implementation at almost any function point to change the framework’s default behavior to suit their business needs

This article mainly explains the extension point principle of Dubbo.

I. INTRODUCTION to SPI

The Service Provider Interface (SPI) in JDK provides an interface-based extension mechanism. The steps are as follows:

  • Define an interface as a standard.
  • Create a meta-INF /services directory in the project implementing the extension point, create a file name with the full class name of the defined interface as the file name, and write the full class name of the implementation class to the file.
  • Call the java.util.Serviceloader #load(java.lang.class) method where the extension point is needed, pass in the full Class name of the interface, return java.util.Serviceloader, ServiceLoader is an implementation class of Iterable that executes all of its extensions via iterators.

For details, please refer to my article about SPI: Java SPI

Dubbo extension details

1. Enhancement of extension points in Dubbo

Extension points in Dubbo are enhancements to the JDK extension point concept, including the following enhancements:

  • The contents of the full class name file are written by the key-value specification and stored in k-V mode during loading, which increases the flexibility of extension point search
  • JDK extension point in the load will be one-time all extension points loaded into memory, if some extension point was useless, but change the extension point initialization is very time-consuming, the JDK will also all extension points loaded into memory, these can cause some waste, and the extension points in Dubbo can according to need to load (load to extension point name, This also depends on the file’s K-V format.)
  • Dubbo adds support for extension points IoC and AOP, where one extension point can directly setter into other extension points. At the same time, when an extension point is injected, the extension point implementation is also wrapped with a scanned Wrapper.

2. How to use extension points in Dubbo

  • Define an interface and label it with an @SPI to indicate that it is an extension point
  • Create a file in the extension point implementation project: / meta-INF /dubbo/ Extension point full class name
  • Define k-V format data for the extension point implementation in the file
helloService=com.bobo.spring.cloud.alibaba.consumer.spi.impl.HelloServiceImpl
Copy the code
  • Call the following code to get the implementation of the extension
HelloService HelloService = ExtensionLoader
						.getExtensionLoader(HelloService.class)
						.getExtension("helloService");
System.out.println(HelloService.sayHello("wangxing"));
Copy the code

3.Dubbo extension point source analysis

Three types of extension points exist in Dubbo (Q group: 463257262) :

  1. Specify the name extension point
  2. Adaptive extension points
  3. Activation extension point

3.1 Adaptive extension point source code analysis

In Dubbo, the interface is identified as an extension point by labeling [@spi] on the interface. At the same time, if there is a annotation [@adaptive] on the extension point implementation class or method, it indicates that the class or method is an Adaptive extension point. Annotation on a class means that the extension class is the default adaptive extension point, annotation on a method means that the method is an adaptive extension point, which overrides the method so that Dubbo can get the specific extension point at run time. Add down into the source code analysis…

The entry to Dubbo’s extension point is as follows:

HelloService helloService2 = ExtensionLoader
								.getExtensionLoader(HelloService.class)
								.getAdaptiveExtension();
Copy the code

First go to the getExtensionLoader() method

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

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
Copy the code

This approach does several things

  • Check whether the incoming extension point is an interface with an @SPI annotation. This confirms our previous statement that only an interface with an @SPI annotation is considered an extension point interface by Dubbo

  • Retrieves ExtensionLoader from the EXTENSION_LOADERS cache by type and returns ExtensionLoader. (EXTENSION_LOADERS is a CurrentHashMap set with key as the. Class object of the extension point interface and value as the ExtensionLoader corresponding to the extension point)

  • If no ExtensionLoader is retrieved from the cache, use the extension point. Class object as the key, create an ExtensionLoader object as the value, store it in EXTENSION_LOADERS, and return the created ExtensionLoader. At this point, an ExtensionLoader can be obtained. The returned ExtensionLoader object can be used to obtain the corresponding extension point implementation object

Next go to the constructor in ExtensionLoader class and see what ExtensionLoader does when instantiated

private ExtensionLoader(Class
        type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
Copy the code

Copy the.class object of the extension point passed in for the Type attribute in the constructor. At the same time, an extension point implementation of ExtensionFactory is obtained by adaptive extension point, which is assigned to objectFactory. We will not specify which implementation will be obtained in detail here, and we will come back to it after the analysis in this section.

Through the above steps, an initialized ExtensionLoader object has been obtained. Analyze the process of getAdaptiveExtension() to obtain an adaptive extension point implementation

Enter getAdaptiveExtension ()

public T getAdaptiveExtension(a) {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
      // If the instance is null, the cache cutting error cannot be null, and an exception is raised
      if(createAdaptiveInstanceError ! =null) {
        throw new IllegalStateException("Failed to create adaptive instance: " +
                                        createAdaptiveInstanceError.toString(),
                                        createAdaptiveInstanceError);
      }

      synchronized (cachedAdaptiveInstance) {
        instance = cachedAdaptiveInstance.get();
        if (instance == null) {
          try {
            instance = createAdaptiveExtension();
            cachedAdaptiveInstance.set(instance);
          } catch (Throwable t) {
            // The exception information is cached, and the next time it comes in, if it is found that the instance is created, it will throw the exception directly. The design here should be to avoid optimizing the creation process multiple times when an extension point creates an exception
            createAdaptiveInstanceError = t;
            throw new IllegalStateException("Failed to create adaptive instance: "+ t.toString(), t); }}}}return (T) instance;
    }
Copy the code

This approach does several things

  • Get the extension point implementation from the cache cachedAdaptiveInstance and return the instance directly if the extension point object exists. CachedAdaptiveInstance is a Holder object that is used to cache concrete instances of the implementation of this extension point, because only one implementation of the adaptive extension point is returned (if there are multiple implementation classes annotated, the last one is taken in file definition order). Implementation For each ExtensionLoader, the adaptive extension point is a singleton.

  • If the extension point implementation does not exist, call createAdaptiveExtension() to create a concrete implementation and cache the instance in cachedAdaptiveInstance.

The process for creating an extension point implementation is in the createAdaptiveExtension method

 privateClass<? > getAdaptiveExtensionClass() { getExtensionClasses();if(cachedAdaptiveClass ! =null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
Copy the code

This approach does several things

  • Call getExtensionClasses(), which, as the name implies, basically gets all the.class objects implemented by the extension point.
  • Returns if the cached cachedAdaptiveClass object is not null. (cachedAdaptiveClass is a class object used to store the implementation of the Adaptive extension point, that is, the implementation class of the extension point has a default Adaptive extension point marked @Adaptive on the class.)
  • If you have any cachedAdaptiveClass for cache object, then call create a cachedAdaptiveClass createAdaptiveExtensionClass, and copy to cachedAdaptiveClass

Next, go to the getExtensionClasses() method first

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;
}
Copy the code

The method first gets the.class object from cachedClasses (which is also a holder, a map collection for all extension implementations of each extension point) and returns it if it exists, Otherwise, the loadExtensionClasses method is called to load the extension point’s classs and store the loaded classes into cachedClasses. Next, go to loadExtensionClasses

privateMap<String, Class<? >> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<? >> extensionClasses =new HashMap<>();

        for (LoadingStrategy strategy : strategies) {
          // Scan extension point implementations in each load policy directory
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
          // Implementation compatibility for Alibaba
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache"."com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }
Copy the code

First call cacheDefaultExtensionName () method. @spi (“dubbo”)public Interface Protocol indicates that the default extension point is the implementation named dubbo. And cacheDefaultExtensionName method is obtained through annotations to the extension point of the default extension point name, assigned to cachedDefaultName

Traverse strategies to obtain multiple loadingStrategies, and obtain the directories to be scanned through stratery.directory(). The following are the implementations of the three default strategies in Dubbo

  • DubboInternalLoadingStrategy –> META-INF/dubbo/internal/
  • DubboLoadingStrategy –>META-INF/dubbo/
  • ServicesLoadingStrategy –> META-INF/services/

In Dubbo, when the ExtensionLoader object is created, it is loaded to all loadingStrategies, using the JDK’s native SPI method. All extension implementations of LoadingStrategy are loaded in and saved into strategies. So if you need to extend the scan path in Dubbo, just do it native to the JDK

Go to the loadDirectory method

private void loadDirectory(Map<String, Class<? >> extensionClasses, String dir, String type,boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            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(); loadResource(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

This method first scans all files under the incoming path with the full class name of Type, obtains resources, converts the obtained files into resources, and passes them to the loadResource() method

private void loadResource(Map<String, Class<? >> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL,boolean overridden, String... excludedPackages) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
              // Read each line of the file in line
                while((line = reader.readLine()) ! =null) {
                    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;
                          Key (extension point name) is used before the equal sign, and the full class name is used after the equal sign
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                                loadClass(extensionClasses, resourceURL, Class.forName(line, 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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in "+ resourceURL, t); }}Copy the code

First, the file is read in line, and each line of data read is divided by = sign. Before = sign is the name of the extension point, and after = sign is the full class name of the concrete extension class of the extension point

The implementation Class is loaded into memory via class.forname. Passed to 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)) {
          	// The saved 'cachedAdaptiveClass' will be overwritten
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
              // If the name in the extension file is empty, the findAnnotationName method is called to get the extension point name
                name = findAnnotationName(clazz);
                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); }}}}Copy the code

This approach does the following things

  • Determines whether the object implements an extension point interface and throws an exception if it does not
  • To determine whether the instance is an adaptive extension point, call the cacheAdaptiveClass method to copy the extension point into the cachedAdaptiveClass member variable.
  • To determine if the instance is a wrapper for an extension point, call the cachedWrapperClasses method to save the instance to cachedWrapperClasses. Whether an extension point implementation is a wrapper is determined if the implementation class has a constructor that takes an extension point as an argument.
  • The name is segmented to obtain a single extension point name and check whether it is an extension point. If yes, the instance is stored in cachedAttributes
  • The name of the cache extension point, which is stored in cachedNames, is used to implement the.class key of the extension point and the name of the extension point is value
  • Call the saveInExtensionClass method to save the extension point name and its implemented. Class to the extensionClasses() collection.

At this point, all implementations of the extension point in the project will have been loaded, and the different types of implementations, adaptive extension points, Wrappers, and so on, have been distinguished. And then we go back to

Once again return to getAdaptiveExtensionClass () method, when performed getExtensionClasses (); After the method, if cacheAdaptiveClass is null, it means that the extension point has no default Adaptive extension point. At this time, the extension point needs to mark @adaptive () on the method that requires Adaptive extension, and the METHOD needs to pass in the URL object. Because the configuration needs in Dubbo will all be carried through the URL.

Will call createAdaptiveExtensionClass () method to dynamically create an adaptive extension point

privateClass<? > createAdaptiveExtensionClass() { String code =new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
Copy the code

This method dynamically generates a class that ADAPTS to the extension point, then compiles it through the compiler and loads its.class file into memory. Returns a. Class object for this dynamic class.

The generated dynamic class code is as follows:

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

public class HelloService$Adaptive implements com.wangx.spring.cloud.alibaba.consumer.spi.HelloService {
    public java.lang.String sayHello(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
      // Default class name split. You can specify the name of this parameter in the @adaptive annotation, and default is the default extension point implementation defined on SPI
        String extName = url.getParameter("hello.service"."default");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
      // Get the extension implementation of the extension point type by namecom.wangx.spring.cloud.alibaba.consumer.spi.HelloService extension = (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) ExtensionLoader.getExtensionLoader(com.wangx.spring.cloud.alibaba.consumer.spi.HelloService.class).getExtension(extName) ;returnextension.sayHello(arg0, arg1); }}Copy the code

This class implements the extension point interface and overrides the adaptive extension method in the extension point. This method reflects the implementation of the extension point processing the current URL dynamically through the information of the incoming URL at run time.

From there, we can return the.class object of the adaptive extension point in each case, and then go back to the createAdaptiveExtension method. Through the above series of operations, we have obtained the.class object of the adaptive extension point and called reflection to create an object that the extension point implements. InjectExtension is then called for dependency injection

private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if(! isSetter(method)) {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];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    String property = getSetterProperty(method);
                    Object object = objectFactory.getExtension(pt, property);
                    if(object ! =null) { method.invoke(instance, object); }}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

In the injectExtension method, there are the following operations:

  • Check whether the objectFactory exists. If the value is null, the instance object is returned
  • All methods of the object are iterated, and the methods that are not setter methods and are annotated with @DisableInject are filtered. The setter method parameters of the table are of specific types and native types, and the parameters of the setter method are the objects to be injected.
  • According to the setter method to obtain the dependency injection property names, and then through the objectFactory. GetExtension (pt, the property); Get the injected object instance and execute the setter method for dependency injection.

When initializing the ExtensionLoader object, the objectFactory is obtained by the following code

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
Copy the code

Through the above column analysis, we can now know that this is an adaptive extension point of the ExtensionFactory. Based on the way we get the adaptive extension point, we can infer the implementation of the adaptive extension point.

In all subclass implementations of ExtensionFactory, we find the AdaptiveExtensionFactory class, This class is tagged @adaptive, so it can be inferred that objectFacotory refers to an object of the AdaptiveExtensionFactory class.

Let’s look at the implementation in the AdaptiveExtensionFactory class:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory(a) {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

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

In the AdaptiveExtensionFactory constructor, Will get to ExtensionFactory types of ExtensionLoader first, then by calling the loader. GetSupportedExtensions () to get to the names of all the extension implementation of ExtensionFactory. Get all extension points by name through loader.getextension (name) and store them in factories.

When the getExtension() method is called in injectExtension, all ExtensionFactory extension points obtained at initialization are iterated. Returns an instance of the extension object as long as it is found at that point in one of the extensions. Objects type and name are passed in to get extension points that may exist in a Dubbo environment.

The injectExtension completed object returned in the injectExtension method is the adaptive extension point object we need to get

3.2 Obtaining extension points by name

Get an extension point by name. As the name implies, get the corresponding implementation of the extension point by the name of the extension point. This method is also widely used in Dubbo, mainly through the parameters or protocols in the URL as the name, at run time according to the URL dynamic access to different ways of implementation. Such as obtaining load balancers

The entrance is as follows:

HelloService HelloService = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension("helloService");
Copy the code

The getExtensionLoader method was explained above, so now we go directly to the getExtension method

public T getExtension(String name) {
  // Check whether the incoming name is null
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
  // If true, get the default extension point, getExtensionClasses->loadExtensionClasses is called in the getDefaultExtension method, The method of cacheDefaultExtensionName will default extension point name assignment to cachedDefaultName, so when the getDefaultExtension () can get the default extension point
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
  
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) { instance = createExtension(name); holder.set(instance); }}}return (T) instance;
    }
Copy the code

This method does the following

  • If the name passed in is true, the default extension point for the extension point is obtained
  • Obtain or create a Holder, which is different from the adaptive extension point, where only one implementation of the adaptive extension point is saved. When obtaining an extension point by name, you need to store the Holder wrapped by the corresponding extension point implementation of each name in cachedInstances. CachedInstances is a map collection that holds the implementation of the extension corresponding to name. If the holder does not exist, a new instance is created to retrieve the instance saved by the holder. If the holder does not exist, a single instance is created to store in the holder using the double-checked lock.

To create an instance, call the createExtension(name) method

@SuppressWarnings("unchecked")
private T createExtension(String name) { Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
    throw findException(name);
  }
  try {
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if (CollectionUtils.isNotEmpty(wrapperClasses)) {
      for(Class<? > wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } 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

From the analysis of adaptive extension points, we already know that the getExtensionClasses() method returns a class object whose extension point name is the key and value is the implementation class of the concrete extension point. You can then get the class object corresponding to the extension point by name. The class object is then used to fetch the object of the implementation class from the EXTENSION_INSTANCES cache, or create an object directly through reflection if none exists.

Next, as with the adaptive extension point, injectExtension is called for dependency injection. Once dependency injection is complete, the object is wrapped, first fetching all wrapper extension points from the cachedWrapperClasses cache loaded at load time, iterating through all decorators, passing the actual extension point object created into the Wrapper through the constructor. Reflection creates a Wrapper object, performs dependency injection on the Wrapper object, and copies it to Instance when it’s done.

For example, if there is an extension point S that implements F, and the extension point has three wrappers, A,B, and C, then the structure of the last instance object may have gone through layers of nesting and become something like this: A(B(C(F))). When instance is called, the object’s methods are executed from the outermost layer, and the methods of the actual extension point are executed from the innermost layer. This design makes the implementation of packaging more concise and flexible.

The initExtension method is then called, or the Initialize method if the object implements the Lifecycle interface. Finally, a wrapped object is returned

3.3 Activating extension points

Activating the use of extension points also requires the @Activate annotation on the implementation class. Group and value can be specified in the annotation. If they are not specified, they will be activated unconditionally; otherwise, they will be activated according to the specified conditions. The entry of activation extension point is:

List<HelloServiceActive> li  = ExtensionLoader.getExtensionLoader(HelloServiceActive.class).getActivateExtension(url,"helloService2");
        for (HelloServiceActive helloServiceActive : li) {
            helloServiceActive.say();
        }
Copy the code

You need to pass in a URL object and a parameter to get the active extension point to look at the key, such as URL = url.addParameter(“xing”,”xing”); The key passed in is xing. Enter the getActivateExtension() method, because you can specify groups, etc., the final method called is

public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        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 if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if(isMatchGroup(group, activateGroup) && ! names.contains(name) && ! names.contains(REMOVE_VALUE_PREFIX + name) && isActive(activateValue, url)) { activateExtensions.add(getExtension(name)); } } activateExtensions.sort(ActivateComparator.COMPARATOR); } List<T> loadedExtensions =new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if(! name.startsWith(REMOVE_VALUE_PREFIX) && ! names.contains(REMOVE_VALUE_PREFIX + name)) {if (DEFAULT_KEY.equals(name)) {
                    if(! loadedExtensions.isEmpty()) { activateExtensions.addAll(0, loadedExtensions); loadedExtensions.clear(); }}else{ loadedExtensions.add(getExtension(name)); }}}if(! loadedExtensions.isEmpty()) { activateExtensions.addAll(loadedExtensions); }return activateExtensions;
    }
Copy the code

After our analysis of the above two ways, in fact, we can easily understand the third way. The main process is as follows: firstly, all activated extension points loaded in cachedfield are scanned and matched with URL and group. If the match is successful, the extension points are obtained according to the name of the extension point and stored in list. It then iterates through the name of the extension point resolved by the value obtained by the key, loads the extension point into the list, and returns activateExtensions();

This completes the retrieval of extension points by activating them.