SPI signifie Service Provider Interface, qui est un ensemble d'interfaces fournies par Java pour être implémentées ou étendues par des tiers. Il peut être utilisé pour activer des extensions de framework et remplacer des composants. Le rôle de SPI est de trouver des implémentations de service pour ces API étendues. Une implémentation SPI classique en Java est le mécanisme de chargement du pilote JDBC. Lorsque nous avons appris JDBC pour la première fois, nous devions utiliser Class.forName ("com.mysql.jdbc.Driver") pour charger le pilote de base de données. En fait, nous pouvons omettre cette étape dans la dernière version du JDK, qui a été chargée dans le JDK via SPI. Il existe un bloc statique dans la classe DriverManager pour charger le pilote de base de données, le code est le suivant:
static {
//加载并初始化驱动
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
Nous analysons ici la méthode loadInitialDrivers (); en détail. La logique principale de cette méthode est en deux parties. La première consiste à obtenir le nom de classe complet du pilote à partir de la propriété système jdbc.drivers, puis à charger l'initialisation. La seconde consiste à utiliser le mécanisme SPI pour charger la classe de pilote. . Le code est comme suit:
private static void loadInitialDrivers() {
//从系统属性jdbc.drivers获取驱动的全类名,代码不再展示
//通过SPI机制加载初始化驱动
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通过ServiceLoader加载驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
//初始化实例化驱动
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
......
//加载实例化从系统属性获取的驱动,代码不再展示
}
Comme dans le code ci-dessus, nous nous concentrons principalement sur le chargement du pilote via ServiceLoader, qui est le mécanisme SPI. ServiceLoader charge le fichier avec le nom complet de l'interface sous le répertoire META-INF / services, et le contenu du fichier est la classe d'implémentation de l'interface. Par exemple, dans notre mysql-connection.jar, il y a un fichier nommé java.sql.Driver dans son répertoire META-INF / services, dont le contenu est com.mysql.jdbc.Driver et com.mysql.fabric.jdbc. Chauffeur. Ci-dessous, nous présentons comment il charge et initialise ces deux instances. Dans la méthode ci-dessus, il existe une méthode driversIterator.hasNext (). Le code source est le suivant:
public boolean hasNext() {
if (acc == null) {
//调用hasNextService()方法
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
//调用hasNextService()方法
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//加载META-INF/service下的名称为接口全名称的文件
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//其他逻辑,解析文件
}
La méthode ci-dessus charge un fichier dont le nom est META-INF / services comme nom complet de l'interface. Il s'agit de la logique suivante pour analyser le fichier, puis initialiser et instancier la classe d'implémentation configurée dans le fichier. Nous n'introduirons pas le code suivant. Utilisons ServiceLoader comme exemple. Comme suit, nous créons une interface SPIService et deux classes d'implémentation. Le code est le suivant:
public interface SPIService {
void printSpiService();
}
public class CustomizedSPIService implements SPIService {
public void printSpiService() {
System.out.println("I am CustomizedSPIService");
}
}
public class MicroserviceSPIService implements SPIService {
public void printSpiService() {
System.out.println("I am MicroserviceSPIService");
}
}
Ci-dessous, nous n'utilisons pas new pour instancier les deux classes d'implémentation ci-dessus, mais les chargeons via ServiceLoader, car la classe d'implémentation implémente SPIService, nous créons donc d'abord un nom cn.org.microservice sous META-INF / services. inter.SPIService et écrivez les noms de classe complets des deux classes d'implémentation, comme indiqué ci-dessous:
Voici le code de test, qui appellera les méthodes de deux classes d'implémentation, puis imprimera le contenu:
public class ServiceLoaderAp {
public static void main(String[] args) {
ServiceLoader<SPIService> services = ServiceLoader.load(SPIService.class);
services.forEach((spiService) ->{
spiService.printSpiService();
});
}
}
Le mécanisme SPI de Java nous permet d'étendre notre application sans changer le package de code source, mais il présente également certaines lacunes, telles que l'incapacité de conteneuriser, c'est-à-dire l'incapacité de remettre des objets instanciés au conteneur pour la gestion. Par conséquent, de nombreux frameworks sont des mécanismes SPI personnalisés, tels que Spring Boot et le dubbo open source d'Alibaba. Ci-dessous, nous présenterons ces deux mécanismes SPI. Tout d'abord, nous présentons le mécanisme SPI de Dubbo.
La logique associée au SPI de Dubbo est encapsulée dans la classe ExtensionLoader, via ExtensionLoader, nous pouvons charger la classe d'implémentation spécifiée. Dubbo chargera les fichiers nommés d'après le nom de chemin complet de l'interface à partir des répertoires META-INF / services, META-INF / dubbo et META-INF / dubbo / internal. Contrairement au mécanisme SPI intégré de JDK, dubbo fournit des clés valeur-clé. Le droit chemin. Prenons l'interface ci-dessus comme exemple. Nous créons un répertoire dubbo sous le répertoire META-INF, mais le contenu du fichier est modifié comme suit:
customizedSpiService = cn.org.microservice.inter.impl.CustomizedSPIService microserviceSpiService = cn.org.microservice.inter.impl.MicroserviceSPIService
Le code suivant est le code de test du SPI de dubbo. Quant à l'implémentation de dubbo ExtensionLoader, nous ne l'expliquerons pas ici. Nous expliquerons le mécanisme d'extension de dubbo en détail plus tard. Voici juste pour présenter l'utilisation d'ExtensionLoader.
ExtensionLoader<> extensionLoader = ExtensionLoader.getExtensionLoader(SPIService.class);
SPIService spiService = extensionLoader.getExtension("customizedSpiServcice")
spiService.printSpiService()
Ce que dubbo charge, c'est le répertoire META-INF / services, META-INF / dubbo, META-INF / dubbo / internal pour charger les fichiers nommés d'après le nom de chemin complet de l'interface, tandis que Spring Boot utilise SpringFactoiesLoader. Ce qu'il charge est le contenu du fichier spring.factories dans le répertoire META-INF et l'implémentation du mécanisme Spring Boot SPI. Cela n'est pas expliqué en détail ici. Vous pouvez vous référer au blog: Spring Boot Principle Analysis-Automatic Assembly Principle . Le mécanisme de chargement de Spring Boot est présenté en détail.
Alors pouvons-nous créer notre propre mécanisme SPI? La réponse est que c'est actuellement possible. Je n'introduirai pas de cas spécifiques ici, mais seulement comment obtenir des fichiers dans tous les répertoires sous le chemin de classe. Il existe une méthode getResources () dans l'instance ClassLoader pour obtenir tous les fichiers. Avant cela, vous devez effacer le mécanisme de chargement de classe JVM et le mécanisme ClassLoader. Le code est comme suit:
SpringApplication.class.getClassLoader().getResources("META-INF/microservice/event.factories");
Nous présenterons ici le mécanisme SPI de Java, nous présenterons le mécanisme d'extension de Dubbo et le code source de SPI dans Dubbo en détail plus tard.