Core Annotations (Simulating Dubbo)
1. @SPI Annotation (Marking Extension Interfaces)
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SPI {
// Default extension name
String value() default "";
}
2. @Adaptive Annotation (Simplified Adaptive Methods)
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Adaptive {
String[] value() default {};
}
Defining Test Interface + Implementations
1. Interface (with @SPI)
@SPI("human")
public interface GreetingService {
void greet();
}
2. Implementation A
public class HumanGreeting implements GreetingService {
@Override
public void greet() {
System.out.println("Hello, human!");
}
}
3. Implementation B
public class RobotGreeting implements GreetingService {
@Override
public void greet() {
System.out.println("Greetings, Robot!");
}
}
4. AOP Wrapper Class (Core Concept)
// Wrapper class: must hold target interface object + constructor with interface parameter
public class GreetingWrapper implements GreetingService {
private final GreetingService delegate;
public GreetingWrapper(GreetingService delegate) {
this.delegate = delegate;
}
@Override
public void greet() {
System.out.println("===== Pre-processing =====");
delegate.greet();
System.out.println("===== Post-processing =====");
}
}
Configuration Files (Fixed Path, Simulating Dubbo)
Create file:
resources/META-INF/dubbo/com.example.GreetingService
Content:
human=com.example.HumanGreeting
robot=com.example.RobotGreeting
wrapper=com.example.GreetingWrapper
Core Class: SimpleExtensionLoader (The Heart of Implementation)
This is the simplified version of Dubbo's ExtensionLoader, implementing all core logic in under 300 lines.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Simplified Dubbo ExtensionLoader implementation
* Features: configuration loading, caching, instantiation, IOC, and AOP wrapping
*/
public class SimpleExtensionLoader {
// 1. Cache: one interface corresponds to one ExtensionLoader
private static final Map, SimpleExtensionLoader>> LOADER_CACHE = new ConcurrentHashMap<>();
// 2. Cache: name -> extension class
private final Map> extensionClasses = new ConcurrentHashMap<>();
// 3. Cache: name -> extension instance (final object)
private final Map extensionInstances = new ConcurrentHashMap<>();
// Extension interface
private final Class type;
// Default extension name
private String defaultExtension;
// Fixed scanning path
private static final String CONFIG_DIRECTORY = "META-INF/dubbo/";
// Private constructor, only accessible via getExtensionLoader
private SimpleExtensionLoader(Class type) {
this.type = type;
// Load SPI annotation
SPI spiAnnotation = type.getAnnotation(SPI.class);
if (spiAnnotation != null) {
this.defaultExtension = spiAnnotation.value();
}
// Load configuration files
loadExtensionClasses();
}
/**
* Get extension loader (entry point)
*/
public static SimpleExtensionLoader getExtensionLoader(Class type) {
if (type == null) throw new IllegalArgumentException("Type cannot be null");
if (!type.isInterface()) throw new IllegalArgumentException("Must be an interface");
if (!type.isAnnotationPresent(SPI.class)) throw new IllegalArgumentException("Must have @SPI annotation");
SimpleExtensionLoader loader = (SimpleExtensionLoader) LOADER_CACHE.get(type);
if (loader == null) {
LOADER_CACHE.putIfAbsent(type, new SimpleExtensionLoader<>(type));
loader = (SimpleExtensionLoader) LOADER_CACHE.get(type);
}
return loader;
}
/**
* Get extension instance by name (core method)
*/
public T getExtension(String name) {
if (name == null || name.trim().isEmpty()) throw new IllegalArgumentException("Name cannot be empty");
// Check cache first
T instance = extensionInstances.get(name);
if (instance == null) {
instance = createExtension(name);
extensionInstances.put(name, instance);
}
return instance;
}
/**
* Get default instance
*/
public T getDefaultExtension() {
return getExtension(defaultExtension);
}
/**
* Create extension instance: instantiate -> IOC injection -> AOP wrapping
*/
private T createExtension(String name) {
Class> clazz = extensionClasses.get(name);
if (clazz == null) throw new IllegalArgumentException("Extension not found: " + name);
try {
// 1. Instantiate
T instance = (T) clazz.newInstance();
// 2. IOC dependency injection (simplified)
injectDependencies(instance);
// 3. AOP wrapping (wrapper enhancement)
instance = wrapInstance(instance);
return instance;
} catch (Exception e) {
throw new RuntimeException("Failed to create instance", e);
}
}
/**
* Load configuration files
*/
private void loadExtensionClasses() {
String fileName = CONFIG_DIRECTORY + type.getName();
try {
Enumeration urls = getClass().getClassLoader().getResources(fileName);
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
// Parse key=value
String[] keyValue = line.split("=");
String name = keyValue[0].trim();
String className = keyValue[1].trim();
Class> clazz = Class.forName(className);
extensionClasses.put(name, clazz);
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to load configuration", e);
}
}
/**
* IOC injection (simplified: simulating auto-wiring)
*/
private void injectDependencies(T instance) {
// Real Dubbo scans setter methods, simplified here
// You can implement injection logic here
}
/**
* AOP wrapping (core: find all wrapper classes and wrap them layer by layer)
*/
private T wrapInstance(T instance) {
Class> clazz = instance.getClass();
List> wrappers = new ArrayList<>();
// Find all wrapper classes (constructor parameter is current interface)
for (Class> c : extensionClasses.values()) {
try {
c.getConstructor(type);
wrappers.add(c);
} catch (NoSuchMethodException e) {
// Not a wrapper class, skip
}
}
// Wrap layer by layer
for (Class> wrapper : wrappers) {
try {
Constructor> constructor = wrapper.getConstructor(type);
instance = (T) constructor.newInstance(instance);
} catch (Exception e) {
throw new RuntimeException("Failed to wrap instance", e);
}
}
return instance;
}
}
Testing Implementation (Verifying Functionality)
public class SimpleSPITest {
public static void main(String[] args) {
// 1. Get extension loader
SimpleExtensionLoader loader = SimpleExtensionLoader.getExtensionLoader(GreetingService.class);
// 2. Get default implementation (human)
GreetingService human = loader.getDefaultExtension();
human.greet();
System.out.println("------------------------");
// 3. Get specific implementation (robot)
GreetingService robot = loader.getExtension("robot");
robot.greet();
}
}
Output Results
===== Pre-processing =====
Hello, human!
===== Post-processing =====
------------------------
===== Pre-processing =====
Greetings, Robot!
===== Post-processing =====
Core Concepts in Simplified Dubbo SPI
| Simplified Implementation |
Actual Dubbo Source Code |
Purpose |
| @SPI |
@SPI |
Mark extension interface + default value |
| SimpleExtensionLoader |
ExtensionLoader |
Core loader |
| LOADER_CACHE |
EXTENSION_LOADERS |
Interface → loader cache |
| extensionClasses |
cachedClasses |
name → Class cache |
| extensionInstances |
cachedInstances |
name → instance cache |
| loadExtensionClasses() |
loadClass() |
Scan and load configuration |
| createExtension() |
createExtension() |
Instantiate + inject + wrap |
| wrapInstance() |
wrap() |
AOP wrapper enhancement |
| META-INF/dubbo/ |
Same path |
Extension configuration files |
Key Insights from This Implementation
- Dubbo SPI fundamentally consists of: configuration + reflection + caching + wrapping
- Wrapper classes are the underlying implementation of AOP
- One interface per loader with full caching ensures high performance
- The getExtension(name) process: cache → instantiate → inject → wrap