Dubbo’s runtime discovers and wires implementations through three distinct extension mechanisms:
- Named Extension –
ExtensionLoader.getExtensionLoader(X.class).getExtension("beanName") - Adaptive Extension –
ExtensionLoader.getExtensionLoader(X.class).getAdaptiveExtension() - Activate Extension –
ExtensionLoader.getExtensionLoader(X.class).getActivateExtension(url, key, group)
ExtensionLoader Bootstrap
The first interaction always starts with getExtensionLoader. Internal it:
- Ensures the supplied type is an interface and is annotated with
@SPI. - Checks a concurrent map (
EXTENSION_LOADERS) for an existing loader; if absent, creates and caches one.
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 is not an interface");
if (!type.isAnnotationPresent(SPI.class))
throw new IllegalArgumentException("Extension type lacks @SPI annotation");
return ConcurrentMapUtil.computeIfAbsent(EXTENSION_LOADERS, type, ExtensionLoader::new);
}
The constructor itself delegates the creation of an ExtensionFactory adaptive instance for later dependency injection.
Named Extension Workflow
Calling getExtension("foo") is the most common path:
- A double-checked holder guarantees singleton semantics.
- If the instance is absent,
createExtensionis invoked.
public T getExtension(String name) {
if (StringUtils.isBlank(name)) throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) return getDefaultExtension();
Holder<Object> holder = holders.computeIfAbsent(name, k -> new Holder<>());
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension Breakdown
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) throw new IllegalStateException("No such extension " + name);
T instance = (T) INSTANCES.computeIfAbsent(clazz, c -> {
try { return c.newInstance(); }
catch (Exception ex) { throw new RuntimeException(ex); }
});
injectExtension(instance); // setter injection
for (Class<?> wrapper : cachedWrapperClasses) {
instance = injectExtension(
(T) wrapper.getConstructor(type).newInstance(instance));
}
initExtension(instance); // lifecycle callback
return instance;
}
Extension scanning happens in loadExtensionClasses which iterates over the built-in LoadingStrategy implementations:
META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/
Each file is parsed and the mapping alias -> implementationClass is cached. Lines ending with Wrapper are collected separately so that every concrete instance is automatically wrapped.
Adaptive Extension Generation
When the caller requests getAdaptiveExtension(), Dubbo either returns a user-supplied class annotated with @Adaptive or generates one on the fly using Javassist. The generated class inspects the URL parameter at runtime to decide wich concrete implementation to delegate to.
public T getAdaptiveExtension() {
Object instance = adaptiveInstance.get();
if (instance == null) {
synchronized (adaptiveInstance) {
instance = adaptiveInstance.get();
if (instance == null) {
instance = createAdaptiveExtension();
adaptiveInstance.set(instance);
}
}
}
return (T) instance;
}
The code generator produces a class like Protocol$Adaptive whose export method resembles:
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
String protocolName = invoker.getUrl().getParameter("protocol", "dubbo");
Protocol real = ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension(protocolName);
return real.export(invoker);
}
Activate Extension & Conditional Loading
The @Activate annotation acts like Spring’s @Conditional. It allows multiple implementations to be loaded automatically when the supplied URL matches the declared criteria (group, value, order).
@Activate(group = {CONSUMER, PROVIDER}, value = "cache")
public class CacheFilter implements Filter { ... }
At runtime getActivateExtension(url, key, group) collects all matching implementations, sorts them by order, and returns a list ready for chain execution.
ExtensionFactory & IoC
Every ExtensionLoader owns an objectFactory which is itself an adaptive extension of ExtensionFactory. The default AdaptiveExtensionFactory delegates to both SpiExtensionFactory and SpringExtensionFactory, enabling seamless injection of either Dubbo extensions or Spring beans.
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> delegates;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader =
ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
delegates = loader.getSupportedExtensions().stream()
.map(loader::getExtension)
.collect(Collectors.toList());
}
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : delegates) {
T ext = factory.getExtension(type, name);
if (ext != null) return ext;
}
return null;
}
}
With these three extension modes—named, adaptive, and activate—Dubbo achieves a highly flexible, configuration-driven plugin architecture while maintaining thread safety and minimal runtime overhead.