Mécanisme de traitement des annotations SPI dans Dubbo3
Comment ExtensionLoader traite-t-il l'annotation @SPI?
ExtensionLoader, situé dans le module dubbo-common dans le package extension, fonctionne de manière similaire à java.util.ServiceLoader de JDK SPI. La logique centrale de Dubbo SPI est presque entièrement encapsulée dans ExtensionLoader (y compris la logique de traitement de l'annotation @SPI), comme indiqué dans la définition de classe suivante :
// org.apache.dubbo.common.extension.ExtensionLoader
public class ExtensionLoader<T> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ExtensionLoader.class);
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
private static final String SPECIAL_SPI_PROPERTIES = "special_spi.properties";
// autres déclarations...
}
L'utilisation s'effecute comme suit :
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
1. Champs statiques clés d'ExtensionLoader
- strategies, de type LoadingStrategy
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
La structure de clasce est la suivante :
Les classes d'implémentation de LoadingStrategy héritent toutes de l'interface de priorité Prioritized.
public interface Prioritized extends Comparable<Prioritized> {
/**
* La priorité maximale
*/
int MAX_PRIORITY = Integer.MIN_VALUE;
/**
* La priorité minimale
*/
int MIN_PRIORITY = Integer.MAX_VALUE;
/**
* Priorité normale
*/
int NORMAL_PRIORITY = 0;
/**
* Obtenir la priorité
*
* @return la valeur par défaut est {@link #NORMAL_PRIORITY}
*/
default int getPriority() {
return NORMAL_PRIORITY;
}
}
- Autres champs statiques importants
// Nom du fichier de configuration SPI spécial
private static final String SPECIAL_SPI_PROPERTIES = "special_spi.properties";
// Modèle séparateur de noms
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
// Mappage de stratégie de chargement SPI spécial, stocke le mappage de stratégie de chargement pour certaines interfaces SPI centrales de Dubbo
private static final Map<String, String> specialSPILoadingStrategyMap = getSpecialSPILoadingStrategyMap();
// Cache de liste d'URL, utilise des références douces pour mettre en cache le contenu des ressources URL, évitant la lecture répétée des mêmes fichiers de ressources
private static SoftReference<ConcurrentHashMap<java.net.URL, List<String>>> urlListMapCache =
new SoftReference<>(new ConcurrentHashMap<>());
// Liste des descripteurs de méthodes d'injection à ignorer
private static final List<String> ignoredInjectMethodsDesc = getIgnoredInjectMethodsDesc();
2. Champs d'instance d'ExtensionLoader
// Cache d'instances d'extension
// Crée des instances d'extension pour éviter les duplications
// Utilise la classe d'extension comme clé et l'objet instance comme valeur
private final ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);
// Type et injection de dépendances
// type: Représente le type d'interface d'extension chargé par ce chargeur d'extension
// injector: Injecteur d'extension pour l'injection de dépendances
private final Class<?> type;
private final ExtensionInjector injector;
// Cache de noms
// Cache le mappage entre les classes d'extension et leurs noms correspondants
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// Verrou de chargement de classe et cache de classe
// loadExtensionClassesLock: Garantit la sécurité des threads lors du chargement des classes d'extension
// cachedClasses: Cache les classes d'extension déjà chargées
private final ReentrantLock loadExtensionClassesLock = new ReentrantLock();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// Cache lié à Activate
// cachedActivates: Cache les extensions avec l'annotation @Activate
// cachedActivateGroups: Cache les informations de groupe d'activation
// cachedActivateValues: Cache les valeurs de condition d'activation
private final Map<String, Object> cachedActivates = Collections.synchronizedMap(new LinkedHashMap<>());
private final Map<String, Set<String>> cachedActivateGroups = Collections.synchronizedMap(new LinkedHashMap<>());
private final Map<String, String[][]> cachedActivateValues = Collections.synchronizedMap(new LinkedHashMap<>());
// Cache d'instances
// Cache les instances d'extension créées, supporte l'accès par nom
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// Cache d'extension adaptative
// cachedAdaptiveInstance: Cache l'instance d'extension adaptative
// cachedAdaptiveClass: Cache la classe d'extension adaptative
// createAdaptiveInstanceError: Information d'erreur lors de la création d'instance adaptative
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
private volatile Throwable createAdaptiveInstanceError;
// Nom d'extension par défaut
// Stocke le nom d'extension spécifié par @SPI
private String cachedDefaultName;
// Cache de classes wrapper
private Set<Class<?>> cachedWrapperClasses;
// Gestion des exceptions
// exceptions: Stocke les exceptions survenues lors du chargement des extensions
// unacceptableExceptions: Stocke les exceptions inacceptables (comme les extensions en double)
private final Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
private final Set<String> unacceptableExceptions = new ConcurrentHashSet<>();
// Gestion d'extension
// extensionDirector: Gestionnaire d'extension
// extensionPostProcessors: Liste des post-processeurs d'extension
// instantiationStrategy: Stratégie d'instanciation
// activateComparator: Comparateur d'extension d'activation
// scopeModel: Modèle de portée
// destroyed: Marqueur si le chargeur d'extension est détruit
private final ExtensionDirector extensionDirector;
private final List<ExtensionPostProcessor> extensionPostProcessors;
private InstantiationStrategy instantiationStrategy;
private final ActivateComparator activateComparator;
private final ScopeModel scopeModel;
private final AtomicBoolean destroyed = new AtomicBoolean();
ExtensionLoader : Chargeur central SPI
Chaque interface SPI correspond à une instance ExtensionLoader, obtenue via ExtensionLoader.getExtensionLoader(Class type).
Méthodes principales
| Méthode | Description |
|---|---|
| getExtension(String name) | Récupère l'instance d'extension spécifiée (singleton, chargement paresseux) |
| getAdaptiveExtension() | Récupère l'instance d'extension adaptative (générée dynamiquement ou implémentée manuellement) |
| getActivateExtension(URL url, String key, String group) | Récupère la liste des extensions actives répondant aux conditions |
| getExtensionClasses() | Charge toutes les classes d'extension (sans instanciation) |
| getDefaultExtension() | Récupère l'extension par défaut (spécifiée par @SPI) |
Processus de chargement
Prenons getExtension("dubbo") comme exemple
- Vérification du cache : L'extension a-t-elle déjà été chargée ?
- Chagrement du fichier de configuration : Balayage de
META-INF/dubbo/...pour obtenir tous les mappages entre noms et classes d'extension. - Instanciation par réflexion : Création d'objet via
Class.newInstance(). - Injection de dépendances (IoC)
- Si la classe d'extension a des méthodes setter avec des paramètres de type autre interface SPI,
- automatiquement récupère leur instance depuis ExtensionLoader et les injecte.
- Wrapping
- Recherche de toutes les classes Wrapper ;
- Emboîtement séquentiel de l'instance originale pour former une chaîne AOP.
- Retour de l'instance finale.
Tout le processus est sécurisé pour les threads et les instances sont par défaut singleton (peut être contrôlé par Scope).