我想了解this
我写了一个使用这种技术的例子:
Project structure:
Service provider interface(SPI)
主要:
ReportRenderer reportRenderer = ReportRenderer.getInstance();
System.out.println(reportRenderer.getClass());
ReportRenderer:
public class ReportRenderer {
public static ReportRenderer getInstance() {
final Iterator<ReportRenderer> providers = ServiceLoader.load(ReportRenderer.class).iterator();
if (providers.hasNext()) {
return providers.next();
}
return new ReportRenderer();
}
FileReportRenderer:
public class FileReportRenderer extends ReportRenderer {...
META-INF/services/my.spi.renderer.ReportRenderer
的内容:
my.spi.renderer.FileReportRenderer
我已经创建了jar并启动了应用程序:
D:\work\SPI_test\build\libs>java -jar SPI_test.jar
class my.spi.renderer.FileReportRenderer
这很清楚。但我不明白如何在我的实际应用中使用这个技巧。我可以获得哪些好处?
Wiki说JDBC
使用这种技术。
我发现了
mysql-connector-java-5.1.16-bin.jar 内部有相关内容:
MySQL的连接器的Java-5.1.16-bin.jar \ META-INF \服务\ java.sql.Driver中
它包含:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
请澄清connectir如何使用SPI以及为什么?它只是通过imlement的方式吗?
答案 0 :(得分:2)
服务定义允许动态发现接口(或类)的实现。例如,在JDBC中,java.sql.DriverManager
使用服务机制来查找和加载java.sql.Driver
的实现。
具体来说(Java 9中的代码):
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
// ...
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
您的代码不需要知道将使用哪个驱动程序(因此,不需要Class.forName(<nameOfDriverClass>)
),它只需要在运行时出现。因此,无需显式加载或配置要使用的驱动程序类。您真正需要的唯一事情是JDBC URL(可能由配置或其他方式提供),DriverManager
将尝试所有加载的驱动程序,直到其中一个驱动程序建立连接。这意味着如果您有不同的驱动程序支持相同的URL,那么您可以交换驱动程序。或者,如果您想使用不同的数据库,您可以将其驱动程序放在类路径上并配置正确的URL(我忽略了SQL方言的潜在问题)。
所以回答后续问题:
阐明connectir如何使用SPI以及为什么
Connector / J本身不使用服务加载器机制,Java本身用于加载驱动程序。 Connector / J实现了这一点,因为自JDBC 4以来JDBC规范需要这样做。
JDBC有一个警告:驱动程序需要位于要自动加载的初始(系统)类路径上。位于其他类路径上的驱动程序(例如Web应用程序内的驱动程序)可能仍需要明确加载。
如果您在Java内部进行搜索,您会发现ServiceLoader
被广泛使用,例如:
java.awt.Toolkit
使用它来加载javax.accessibility.AccessibilityProvider
实现,这允许Java为辅助功能加载特定于平台的支持java.net.URL
使用它来加载java.net.spi.URLStreamHandlerProvider
实现,这允许您添加对不同网络协议的支持(例如http,ftp等)java.nio.charset.Charset
使用它来加载java.nio.charset.spi.CharsetProvider
,这允许您向Java添加额外的字符集工具和库可以使用服务加载器机制来支持插件/扩展。在您自己的示例中,另一个库,甚至是您库的用户,可以实现自己的ReportRenderer
,并让您的工具/库自动发现和使用它。