Code Changes Before and After Using SPI
Loading JDBC Driver Implementations
Before adopting the SPI mechanism, establishing a database connection with JDBC typically required explicit driver class loading:
// Manually load the MySQL Driver implementation
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "pass");
After implementing SPI, DriverManager.getConnection() can be invoked directly without manual class loading.
SLF4J Locating Logging Implementations
Previously, SLF4J required each concrete loggging implementation to provide a specific binder class (org.slf4j.impl.StaticLoggerBinder) to establish the binding, functioning similarly to SPI's configuraton files. With SPI, this binding is achieved through the SLF4JServiceProvider interface.
Practical SPI Implementation Example
Consider an Extension interface. A client uses the ExtensionManager class to load and execute all discovered implementations via its activateExtensions method.
// Extension interface
public interface Extension {
void activate(Configuration config);
}
public class ExtensionManager {
public void activateExtensions() {
Configuration config = new Configuration();
config.set("beans", new ArrayList<>());
config.set("version", "2.1.0");
config.set("features", new HashMap<>());
// Load all Extension implementations using ServiceLoader
ServiceLoader<Extension> loader = ServiceLoader.load(Extension.class);
for (Extension ext : loader) {
ext.activate(config);
}
}
public static void main(String[] args) {
ExtensionManager manager = new ExtensionManager();
manager.activateExtensions();
}
}
Implementation of the Extension interface:
public class AuditExtension implements Extension {
private static final Logger LOG = LoggerFactory.getLogger(AuditExtension.class);
@Override
public void activate(Configuration config) {
LOG.debug("Initializing audit extension...");
// Initialization logic here
}
}
A key advantage of SPI is the ability to switch implementations without modifying client code. This is done by changing the dependency in the build configuration (e.g., Maven) or replacing the corresponding JAR file in the classpath and restarting the service.
SPI Implementation Mechanism
The ServiceLoader.load() method operates by reading a file named after the fully qualified interface name from the META-INF/services/ directory. This file contains the fully qualified names of the implementing classes. ServiceLoader then loads each class, uses reflection to instantiate it via its no-argument constructor, and returns the instances.