The Java native SPI mechanism loads all the implementation classes, and often we only need one of them, resulting in a waste of resources. Dubbo SPI not only addresses this waste of resources, but also extends and modifies it.

Dubbo divides SPI profiles into three categories of directories for their purpose:

  1. Meta-inf /services/ directory: The SPI configuration file in this directory is used to be compatible with JDK SPI.

  2. Meta-inf /dubbo/ directory: This directory is used to store user-defined SPI configuration files.

  3. Meta-inf /dubbo/internal/ : This directory is used to store SPI configuration files for internal use in Dubbo.

    Dubbo changes the SPI configuration file to KV format, with key as the extension and value as the concrete implementation class, so that we can get the specified implementation directly from the extension. To org. Apache. Dubbo. RPC. Protocol, for example:

registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
Copy the code

1. Core implementation

Dubbo SPI core logic in class org.apache.dubbo.com mon. The extension. ExtensionLoader.

1.1 @ SPI annotation

@spi is used to modify an interface, indicating that the interface is an extension interface, and the value of the annotation is the default extension.

/** * Protocol. (API/SPI, Singleton, ThreadSafe) */
@SPI("dubbo")
public interface Protocol {
    // omit the method
}
Copy the code

Specific acquisition method:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
Copy the code

1.2 LoadingStrategy

Extended class loading policies correspond to the three types of SPI profiles described above

  • org.apache.dubbo.common.extension.ServicesLoadingStrategy
  • org.apache.dubbo.common.extension.DubboLoadingStrategy
  • org.apache.dubbo.common.extension.DubboInternalLoadingStrategy

1.3 Creating extended Class Instances

Org.apache.dubbo.com mon. The extension. ExtensionLoader# createExtension method core process is as follows:

  1. Gets the concrete implementation class based on the extension
  2. Get the instance from the cache based on the class, return it immediately if successful, otherwise use reflection to create the instance
  3. Automatically inject properties in the instance object (injectExtension), calling setter methods of the instance for injection
  4. Automatic packaging, similar to decorator mode
  5. To achieve theorg.apache.dubbo.common.context.LifecycleInstantiate (initExtension)

1.4@Adaptive annotations and adapters

The @Adaptive annotation is used to implement Dubbo’s adaptor functionality. The @adaptive annotation can modify an interface or method.

  • Modify the interface. The ExtensionFactory interface in Dubbo has three implementation classes, as shown in the figure below, with the @SPI annotation on the ExtensionFactory interface and the @Adaptive annotation on the AdaptiveExtensionFactory implementation class. The AdaptiveExtensionFactory does not contain any concrete implementation, but is responsible for choosing which specific ExtensionFactory interface implementation to create the extension instance.

  • Modify interface methods, Dubbo dynamically generates adapter classes. For example,TransporterThe interface has two coverts@AdaptiveA method of annotation:
@SPI("netty") 
public interface Transporter { 
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) 
    RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException; 
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) 
    Client connect(URL url, ChannelHandler handler) throws RemotingException; 
}
Copy the code

Dubbo generates a Transporter$Adaptive adapter class that inherits the Transporter interface:

public class Transporter$Adaptive implements Transporter { 
    public org.apache.dubbo.remoting.Client connect(URL arg0, ChannelHandler arg1) throws RemotingException { 
        if (arg0 == null) throw new IllegalArgumentException("url == null"); 
        URL url = arg0; 
        // Determine the extension from the client parameter in the URL, followed by the transporter parameter
        // These two parameter names are specified by the @adaptive annotation, followed by the default values in the @spi annotation
        String extName = url.getParameter("client",
            url.getParameter("transporter"."netty")); 
        if (extName == null) 
            throw new IllegalStateException("..."); 
        // Load the specified extended implementation of the Transporter interface through ExtensionLoader
        Transporter extension = (Transporter) ExtensionLoader 
              .getExtensionLoader(Transporter.class) 
                    .getExtension(extName); 
        returnextension.connect(arg0, arg1); }...// omit the bind() method
}
Copy the code

Dynamically generated adapter class logic in org.apache.dubbo.com mon. The extension. ExtensionLoader# createAdaptiveExtensionClass

The adapter class and its instances are stored in the cachedAdaptiveClass and cachedAdaptiveInstance properties of the ExtensionLoader class.

1.5 Automatically inject injectExtension

Dubbo will inject the instance automatically after it is created. Key logic in org.apache.dubbo.com mon. The extension. ExtensionLoader# injectExtension

Methods that meet the following criteria are automatically injected:

  1. Method ispublicmodified
  2. In order tosetAt the beginning
  3. There is only one parameter and it is not a base type
  4. The method has not been@DisableInjectAnnotations to modify

Dubbo relies on the ExtensionFactory interface for injection and currently has three implementation classes:

  • SpringExtensionFactory: Inject the Spring Bean based on the property name
  • SpiExtensionFactory: Gets the adapter implementation based on the extension interface
  • AdaptiveExtensionFactory: An adapter implementation of ExtensionFactory

1.6 Automatic Packaging

Dubbo abstracts the common logic of multiple extension implementation classes into a “wrapper class”. The “wrapper class” implements the extension interface just like ordinary extension implementation classes. When obtaining the real extension implementation object, a “wrapper class” object is wrapped around it, which can be understood as a layer of decorator.

The judgment is “wrapper class” logic: contains a constructor that takes only one argument and is of extended interface type

private boolean isWrapperClass(Class
        clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false; }}Copy the code

Key logic of packaging:

List<Class<? >> wrapperClassesList =new ArrayList<>();
if(cachedWrapperClasses ! =null) {
    wrapperClassesList.addAll(cachedWrapperClasses);
    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
    Collections.reverse(wrapperClassesList);
}

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))) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}}Copy the code

1.7 @activate annotation and automatic activation feature

This feature allows you to return specific extensions based on specific URL requests.

While scanning classes, Dubbo cache the implementation classes modified with the @Activate annotation into the cache CAChedAttributes. The @activate annotation annotates the group, value, and order attributes on the extension implementation class.

  • Group property: Modifies whether the implementation class is activated on the Provider side or on the Consumer side.

  • Value property: The modified implementation class is activated only when the specified key is present in the URL parameter.

  • Order property: Used to determine the order of the extension implementation classes.

The key logic in org.apache.dubbo.com mon. The extension. ExtensionLoader# getActivateExtension (org.apache.dubbo.com mon. URL, java.lang.String[], java.lang.String)

public List<T> getActivateExtension(URL url, String[] values, String group) {... }Copy the code

Get the extension process:

  1. Gets the collection of extensions activated by default. traversecachedActivates, get an extension that meets the following criteria and add it toactivateExtensionsIn the collection.
    1. To expand on the@ActivateThe group attribute specified by the annotation matches the current group
    2. The extension is not present in the input parameter values (neither explicitly specified nor explicitly deleted in the input parameter values, which is indicated by the “-extension”)
    3. To expand on the@ActivateThe value attribute specified by the annotation matches the request parameter that appears in the URL and determines the logic in the methodExtensionLoader#isActive
  2. Gets the extension with the specified input parameter values
    1. If Values contains default, use default as the dividing line, and place the extension that comes before default in front of the activateExtensions collection, followed by the rest.
    2. If values does not contain default, place the extensions inactivateExtensionsThe back of the set.

Finally, a simple example is given to illustrate the above processcachedActivatesThe extended implementation of the collection cache is shown in the following table:DemoFilter3, -Demofilter2, default, demoFilter1, demoFilter1

  1. The set of extension implementations that are activated by default are [demoFilter4, demoFilter6];

  2. [demoFilter6, demoFilter4];

  3. Add custom extension instances in order to get [demoFilter3, demoFilter6, demoFilter4, demoFilter1].

reference

Dubbo SPI refinement, interface to achieve bipolar reversal (part 2)

Dubbo 2.7 source