Backend Development 26 min read

Understanding Dubbo SPI: Features, Implementation and Comparison with JDK SPI

The article explains Service Provider Interface (SPI) concepts, shows how the JDK’s built‑in SPI works, and details Dubbo’s extended SPI mechanism—including named and adaptive extensions, dependency injection, AOP wrappers, and activation filtering—highlighting its richer features and implementation compared to standard Java SPI.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Understanding Dubbo SPI: Features, Implementation and Comparison with JDK SPI

This article introduces the concept of Service Provider Interface (SPI) and explains how Dubbo implements its own SPI mechanism, comparing it with the JDK built‑in SPI.

1. Overview

SPI (Service Provider Interface) is a mechanism that allows modules to reference each other’s implementations via configuration files placed in META-INF/services/ . By adding a new JAR containing a new implementation and its configuration, the consuming code does not need to change. A good SPI framework also supports priority selection among multiple implementations.

2. JDK Built‑in SPI

Providers create a file named after the service interface under META-INF/services/ . The consumer loads implementations with java.util.ServiceLoader .

package com.example.studydemo.spi;
public interface Search {
    void search();
}
package com.example.studydemo.spi;
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
package com.example.studydemo.spi;
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("数据库搜索");
    }
}

The configuration file META-INF/services/com.example.studydemo.spi.Search contains:

com.example.studydemo.spi.DataBaseSearchImpl
com.example.studydemo.spi.FileSearchImpl

Test code:

import java.util.ServiceLoader;
public class JavaSpiTest {
    public static void main(String[] args) {
        ServiceLoader<Search> searches = ServiceLoader.load(Search.class);
        searches.forEach(Search::search);
    }
}

3. Relationship with Parent‑Delegation

ServiceLoader uses a hard‑coded prefix "META-INF/services/" , so configuration files must reside in that directory. The loader can use a specific class loader or the thread‑context class loader, avoiding loading into the bootstrap class loader.

public final class ServiceLoader<S> {
    // hard‑coded path
    private static final String PREFIX = "META-INF/services/";
    ...
}

4. Dubbo SPI

Dubbo extends the JDK SPI idea with an ExtensionLoader class that provides richer features such as named extensions, adaptive extensions, IOC, AOP, and activation filtering.

4.1 Core Concepts

Extension Point : a Java interface.

Extension : an implementation of an extension point.

Extension Instance : a runtime instance of an extension.

Adaptive Extension : a generated proxy that selects an implementation based on URL parameters.

SPI Annotation : marks an interface as an extension point.

@Adaptive : can be placed on a class or method to indicate adaptive behavior.

ExtensionLoader : loads extensions, creates instances, injects dependencies, and supports wrappers for AOP.

Extension Name : each extension has a name defined in configuration files (e.g., registry=com.alibaba.dubbo.registry.integration.RegistryProtocol ).

Loading Paths : Dubbo reads configuration from META-INF/dubbo/internal , META-INF/dubbo , and META-INF/services .

4.2 Dubbo SPI Code Example

package com.example.studydemo.spi;
@SPI("dataBase")
public interface Search {
    void search();
}
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("数据库搜索");
    }
}

Configuration file under META-INF/dubbo/Search :

dataBase=com.example.studydemo.spi.DataBaseSearchImpl
file=com.example.studydemo.spi.FileSearchImpl

Test code:

public class DubboSpiTest {
    public static void main(String[] args) {
        ExtensionLoader<Search> loader = ExtensionLoader.getExtensionLoader(Search.class);
        Search fileSearch = loader.getExtension("file");
        fileSearch.search();
        Search dbSearch = loader.getExtension("dataBase");
        dbSearch.search();
        System.out.println(loader.getDefaultExtensionName());
        Search defaultSearch = loader.getDefaultExtension();
        defaultSearch.search();
    }
}

5. Internal Mechanics of Dubbo SPI

5.1 ExtensionLoader Retrieval

public static
ExtensionLoader
getExtensionLoader(Class
type) {
    if (type == null) throw new IllegalArgumentException("Extension type == null");
    if (!type.isInterface()) throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    if (!withExtensionAnnotation(type)) throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    ExtensionLoader
loader = (ExtensionLoader
) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader
(type));
        loader = (ExtensionLoader
) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

The loader caches the mapping between an interface and its ExtensionLoader . Each loader holds the interface type and an ObjectFactory for dependency injection.

5.2 Adaptive Extension Creation

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        synchronized (cachedAdaptiveInstance) {
            instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                instance = createAdaptiveExtension();
                cachedAdaptiveInstance.set(instance);
            }
        }
    }
    return (T) instance;
}

If no adaptive class exists, Dubbo generates one at runtime using a byte‑code compiler:

private Class
createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    Compiler compiler = ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

Generated code example (for Protocol ):

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() { throw new UnsupportedOperationException(...); }
    public int getDefaultPort() { throw new UnsupportedOperationException(...); }
    public com.alibaba.dubbo.rpc.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        String extName = (arg1.getProtocol() == null ? "dubbo" : arg1.getProtocol());
        com.alibaba.dubbo.rpc.Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {
        // similar logic for export
    }
}

5.3 Dependency Injection (IOC)

private T injectExtension(T instance) {
    if (objectFactory != null) {
        for (Method method : instance.getClass().getMethods()) {
            if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) {
                Class
pt = method.getParameterTypes()[0];
                String property = method.getName().length() > 3 ? method.getName().substring(3,4).toLowerCase() + method.getName().substring(4) : "";
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    method.invoke(instance, object);
                }
            }
        }
    }
    return instance;
}

The AdaptiveExtensionFactory aggregates all ExtensionFactory implementations (e.g., SpiExtensionFactory , SpringExtensionFactory ) and supplies objects for injection.

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    private final List
factories;
    public AdaptiveExtensionFactory() {
        ExtensionLoader
loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List
list = new ArrayList<>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
    public
T getExtension(Class
type, String name) {
        for (ExtensionFactory factory : factories) {
            T ext = factory.getExtension(type, name);
            if (ext != null) return ext;
        }
        return null;
    }
}

5.4 Wrapper Classes and AOP

When an extension has a constructor that accepts the extension point type, Dubbo treats it as a wrapper. Wrappers are instantiated and chained to provide AOP‑style behavior.

public T getExtension(String name) {
    // ... retrieve or create instance
    Set
> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class
wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
}

5.5 Activate Annotation (Active Extensions)

The @Activate annotation marks extensions that should be automatically loaded based on URL parameters, groups, and ordering. The method ExtensionLoader.getActivateExtension returns only those extensions that satisfy the criteria.

public List
getActivateExtension(URL url, String[] names, String group);

Example: a TokenFilter annotated with @Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY) is only loaded on the provider side when the URL contains the token key.

6. Summary

Dubbo’s SPI builds upon the JDK SPI by adding named extensions, adaptive extensions, IOC/AOP support, and integration with external containers such as Spring. It allows developers to add new implementations simply by providing a configuration file, while the framework handles dynamic selection, dependency injection, and optional activation based on runtime context.

JavaaopDubbodependency injectionSPIAdaptiveextensionloader
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.