Le mécanisme de traitement de l'annotation @SPI par ExtensionLoader dans Dubbo3

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
  1. 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;
    }
}

  1. 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

  1. Vérification du cache : L'extension a-t-elle déjà été chargée ?
  2. Chagrement du fichier de configuration : Balayage de META-INF/dubbo/... pour obtenir tous les mappages entre noms et classes d'extension.
  3. Instanciation par réflexion : Création d'objet via Class.newInstance().
  4. 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.
  1. Wrapping
  • Recherche de toutes les classes Wrapper ;
  • Emboîtement séquentiel de l'instance originale pour former une chaîne AOP.
  1. 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).

Étiquettes: dubbo SPI Java extension-loader Annotations

Publié le 4 juillet à 00h49