Apache Dubbo is a powerful RPC framework for building enterprise-grade microservices with service discovery, load balancing, and fault tolerance.
—
Apache Dubbo's SPI (Service Provider Interface) extension system provides a powerful and flexible mechanism for customizing all major framework components. It enables plugin-style architecture where core functionality can be extended or replaced without modifying the core framework.
The ExtensionLoader class provides the core mechanism for loading and managing extensions.
/**
* Extension loader for SPI-based extension management
* @param <T> Extension interface type
*/
public class ExtensionLoader<T> {
/**
* Get extension loader for specific type
* @param type Extension interface class
* @return Extension loader instance
*/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);
/**
* Get extension instance by name
* @param name Extension name
* @return Extension instance
*/
public T getExtension(String name);
/**
* Get adaptive extension that can select implementation at runtime
* @return Adaptive extension instance
*/
public T getAdaptiveExtension();
/**
* Get default extension instance
* @return Default extension instance
*/
public T getDefaultExtension();
/**
* Get activate extensions based on URL parameters
* @param url Service URL with parameters
* @param values Parameter values for activation
* @param group Extension group
* @return List of activated extensions
*/
public List<T> getActivateExtensions(URL url, String[] values, String group);
/**
* Get all supported extension names
* @return Set of extension names
*/
public Set<String> getSupportedExtensions();
/**
* Check if extension exists
* @param name Extension name
* @return True if extension exists
*/
public boolean hasExtension(String name);
/**
* Add extension programmatically
* @param name Extension name
* @param clazz Extension class
*/
public void addExtension(String name, Class<?> clazz);
/**
* Remove extension
* @param name Extension name
*/
public void removeExtension(String name);
/**
* Get loaded extension names
* @return Set of loaded extension names
*/
public Set<String> getLoadedExtensions();
/**
* Get loaded extension instance
* @param name Extension name
* @return Extension instance if loaded
*/
public Object getLoadedExtension(String name);
}Usage Examples:
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Protocol;
// Get protocol extension loader
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
// Get specific protocol implementation
Protocol dubboProtocol = protocolLoader.getExtension("dubbo");
Protocol restProtocol = protocolLoader.getExtension("rest");
// Get adaptive protocol that selects implementation at runtime
Protocol adaptiveProtocol = protocolLoader.getAdaptiveExtension();
// List all available protocols
Set<String> supportedProtocols = protocolLoader.getSupportedExtensions();
System.out.println("Supported protocols: " + supportedProtocols);Extensions are declared using the @SPI annotation on interfaces.
/**
* SPI extension point marker annotation
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SPI {
/**
* Default extension name
* @return Default extension name
*/
String value() default "";
/**
* Extension scope for lifecycle management
* @return Extension scope
*/
ExtensionScope scope() default ExtensionScope.FRAMEWORK;
}
/**
* Extension scope enumeration
*/
public enum ExtensionScope {
FRAMEWORK, // Framework-level singleton
APPLICATION, // Application-level singleton
MODULE, // Module-level singleton
SELF // Instance-level, not singleton
}Usage Examples:
// Declare a custom extension point
@SPI("default")
public interface CustomLoadBalance {
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation);
}
// Implementation
public class MyLoadBalance implements CustomLoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Custom load balancing logic
return invokers.get(0);
}
}Adaptive extensions allow runtime selection of implementations based on URL parameters.
/**
* Adaptive extension annotation for runtime selection
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Adaptive {
/**
* Parameter keys for adaptive selection
* @return Parameter key names
*/
String[] value() default {};
}Usage Examples:
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
// Adaptive method selects implementation based on URL protocol
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
// Adaptive method with custom parameter key
@Adaptive({"protocol"})
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
// Usage - the adaptive extension will select "dubbo" or "rest" protocol
// based on the URL protocol parameter
URL url = URL.valueOf("rest://localhost:8080/service");
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Invoker<Service> invoker = protocol.refer(Service.class, url); // Uses REST protocolThe @Activate annotation enables conditional activation of extensions.
/**
* Extension activation annotation for conditional loading
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Activate {
/**
* Activation groups
* @return Group names
*/
String[] group() default {};
/**
* Activation parameter keys
* @return Parameter keys that trigger activation
*/
String[] value() default {};
/**
* Extensions that must be loaded before this one
* @return Extension names
*/
String[] before() default {};
/**
* Extensions that must be loaded after this one
* @return Extension names
*/
String[] after() default {};
/**
* Activation order (lower value = higher priority)
* @return Order value
*/
int order() default 0;
}Usage Examples:
// Filter that activates for consumer side only
@Activate(group = "consumer", order = -10000)
public class ConsumerContextFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Consumer-side filtering logic
return invoker.invoke(invocation);
}
}
// Filter that activates when cache parameter is present
@Activate(group = {"consumer", "provider"}, value = "cache")
public class CacheFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Caching logic
return invoker.invoke(invocation);
}
}
// Get activated filters
ExtensionLoader<Filter> filterLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = URL.valueOf("dubbo://localhost:20880/service?cache=lru");
List<Filter> filters = filterLoader.getActivateExtensions(url, new String[]{"cache"}, "consumer");Extensions are configured through configuration files in the META-INF/dubbo/ directory.
Configuration File Format:
# META-INF/dubbo/org.apache.dubbo.rpc.Protocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocolDirectory Structure:
META-INF/
├── dubbo/ # Dubbo-specific extensions
├── dubbo/internal/ # Internal extensions
└── services/ # Standard Java SPIWrapper extensions provide AOP-like functionality for decorating other extensions.
/**
* Wrapper extension example
*/
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol) {
this.protocol = protocol;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// Add pre-processing
return protocol.export(buildInvokerChain(invoker));
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// Add post-processing
return buildInvokerChain(protocol.refer(type, url));
}
private <T> Invoker<T> buildInvokerChain(Invoker<T> invoker) {
// Build filter chain
return invoker;
}
}Apache Dubbo provides numerous built-in extension points:
// Core extension interfaces
@SPI("dubbo")
public interface Protocol { /* ... */ }
@SPI("random")
public interface LoadBalance { /* ... */ }
@SPI("failover")
public interface Cluster { /* ... */ }
@SPI("zookeeper")
public interface RegistryFactory { /* ... */ }
@SPI("hessian2")
public interface Serialization { /* ... */ }
@SPI("netty")
public interface Transporter { /* ... */ }
@SPI("javassist")
public interface ProxyFactory { /* ... */ }
@SPI
public interface Filter { /* ... */ }
@SPI("jdk")
public interface Compiler { /* ... */ }Creating Custom Extensions:
// 1. Define extension interface
@SPI("default")
public interface CustomService {
String process(String input);
}
// 2. Implement extension
public class MyCustomService implements CustomService {
@Override
public String process(String input) {
return "Processed: " + input;
}
}
// 3. Create configuration file: META-INF/dubbo/com.example.CustomService
// my=com.example.MyCustomService
// 4. Use extension
ExtensionLoader<CustomService> loader = ExtensionLoader.getExtensionLoader(CustomService.class);
CustomService service = loader.getExtension("my");
String result = service.process("test");Dubbo supports automatic injection of dependencies into extensions.
/**
* Extension with dependency injection
*/
public class MyProtocol implements Protocol {
// Dubbo will automatically inject the LoadBalance extension
private LoadBalance loadBalance;
// Setter injection
public void setLoadBalance(LoadBalance loadBalance) {
this.loadBalance = loadBalance;
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// Use injected loadBalance
return new MyInvoker<>(type, url, loadBalance);
}
}Extensions participate in Dubbo's lifecycle management.
/**
* Lifecycle-aware extension
*/
public class MyExtension implements Protocol, Lifecycle {
private volatile boolean initialized = false;
@Override
public void initialize() throws IllegalStateException {
// Extension initialization logic
initialized = true;
}
@Override
public void start() throws IllegalStateException {
// Extension startup logic
}
@Override
public void destroy() throws IllegalStateException {
// Extension cleanup logic
initialized = false;
}
@Override
public boolean isInitialized() {
return initialized;
}
}Extension Best Practices:
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-dubbo--dubbo