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.
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.FileSearchImplTest 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.FileSearchImplTest 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.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.