What is the SPI
SPI, or Service Provider Interface, is a Service discovery mechanism. It provides an externally extensible capability for the framework.
How does it differ from the RPC approach provided by the interface class – implementation class?
- The traditional interface class implementation form is as follows
public interface AdOpFromApolloService {}
public class AdOpFromDbServiceImpl implements AdOpFromDbService {}
Copy the code
Suppose we need to implement RPC, how do we do it?
RPC adds an annotation to the corresponding interface class AdOpFromApolloService to indicate that it is an RPC class, and then puts the current class in a dependency package for other projects to call through the interface class
In short: Just provide interface classes in RPC calls, and then let third parties call (third parties only call, don’t write implementation)
So what does RPC have to do with SPI?
- SPI: Provides an interface that can be overridden or implemented using the default SPI interface after a third party references the package.
- RPC: Provides the interface, but the implementation is private and does not open the rewriting capability to the public.
Application scenarios of SPI
- Framework implementation cases:
- Spring extension plugin implementation. Such as JDCB
- Middleware extension plug-in implementation. Such as Dubbo, Apollo
- Examples in the development process: implement an interceptor and use SPI mechanism to extend its interception methods (such as full interception, how many intercepts per minute, what proportion of intercepts, whether the intercepted logs are log printed or dropped, es)
How do YOU implement an SPI?
Next, two project modules are used to explain the usage of SPI. First, the project structure diagram is as follows
Next is the implementation process
Step 1: create a spi – demo – contract project, the following new directory in the resources directory (MATE – INF/services) and file (com) example. Spidemocontract. Spi. SpiTestDemoService)
- Modify the com. Example. Spidemocontract. Spi. SpiTestDemoService file
- Add the interface class in the same position as step 2, and the corresponding implementation class
Step 2: Create the SPI-Demo project, and then introduce the SPI-Demo-Contract dependencies
- New directory as follows in the resources directory (MATE – INF/services) and file (com) example. Spidemocontract. Spi. SpiTestDemoService), the filename with spi – demo – contract
- Modify com. Example. Spidemocontract. Spi. SpiTestDemoService (and rely on the package file name completely consistent, but custom content to the current project implementation class) files
- Implement the SPI interface, customize the implementation class of the SPI-Demo project (here you can set the priority to the highest)
Step 3: Load the SPI interface with the ServiceLoader in the SPI-Demo project
Note: We can override the priority of the declaration class to determine which implementation class needs to be handled. For example, if we override a custom implementation class whose priority =0 is the highest, and then load only the first one with the highest priority by default, the custom implementation class we override will override the default SPI implementation class
The detailed steps are divided as follows
- Step 1: create a spi – demo – contract project, the following new directory in the resources directory (MATE – INF/services) and file (com) example. Spidemocontract. Spi. SpiTestDemoService)
-- resources
---- META-INF
-------- services
------------ com.example.spidemocontract.spi.SpiTestDemoService
Copy the code
-
- Modify the com. Example. Spidemocontract. Spi. SpiTestDemoService file is as follows
com.example.spidemocontract.spi.impl.DefaultSpiTestDemoService
Copy the code
-
- Add the interface class in the same position as step 2, and the corresponding implementation class
/ * * * the interface class completely corresponding to the resources/meta-inf/services/com. Example. Spidemocontract. Spi. The impl. DefaultSpiTestDemoService * * /
public interface SpiTestDemoService {
void printLog(a);
int getOrder(a);
}
/** * sets the default to the lowest priority, which is the default SPI interface implementation class */
public class DefaultSpiTestDemoService implements SpiTestDemoService {
@Override
public int getOrder(a) {
return Integer.MAX_VALUE;
}
@Override
public void printLog(a) {
System.out.println("Output DefaultSpiTestDemoService log"); }}Copy the code
- Step 2: Create the SPI-Demo project, and then introduce the SPI-Demo-Contract dependencies
<dependency>
<groupId>com.example</groupId>
<artifactId>spi-demo-contract</artifactId>
<version>0.0.1 - the SNAPSHOT</version>
<scope>compile</scope>
</dependency>
Copy the code
-
- New directory as follows in the resources directory (MATE – INF/services) and file (com) example. Spidemocontract. Spi. SpiTestDemoService), the filename with spi – demo – contract
-- resources
---- META-INF
-------- services
------------ com.example.spidemocontract.spi.SpiTestDemoService
Copy the code
-
- Modify com. Example. Spidemocontract. Spi. SpiTestDemoService (and rely on the package file name completely consistent, but custom content to the current project implementation class) file is as follows
com.example.spidemo.spi.OtherSpiTestDemoService
Copy the code
-
- Implement SPI interface, custom SPI-Demo project implementation class
/** * other, set it to a higher priority */
public class OtherSpiTestDemoService implements SpiTestDemoService {
// When we use SPI class loader, we will sort it according to order, the smaller the higher the priority
@Override
public int getOrder(a) {
return 0;
}
@Override
public void printLog(a) {
System.out.println("Output OtherSpiTestDemoService log"); }}Copy the code
- Step 3: Load the SPI interface with the ServiceLoader in the SPI-Demo project
public static void main(String[] args) {
/ / load SPI
Iterator<SpiTestDemoService> iterator = ServiceLoader.load(SpiTestDemoService.class).iterator();
// Implement ordered, the order will be based on the ordered value, the higher the priority, the more first fetched
List<SpiTestDemoService> list = Lists.newArrayList(iterator)
.stream().sorted(Comparator.comparing(SpiTestDemoService::getOrder))
.collect(Collectors.toList());
for(SpiTestDemoService item : list) { item.printLog(); }}Copy the code
How does middleware implement SPI?
Apollo-client implementation
- Apollo – Client initialization process, there is a ConfigPropertySourcesProcessorHelper SPI interface
/ / the current interface in the resource/meta-inf/services directory file com. Ctrip. Framework. Apollo. Spring. The spi. ConfigPropertySourcesProcessorHelper
public interface ConfigPropertySourcesProcessorHelper extends Ordered {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
Copy the code
- The default implementation in SPI currently is
public class DefaultConfigPropertySourcesProcessorHelper implements ConfigPropertySourcesProcessorHelper {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
/ /... Various registered beans, initialization process
}
@Override
public int getOrder(a) {
// Set the priority to the lowest. Convenient for other custom SPI implementation classes to override
returnOrdered.LOWEST_PRECEDENCE; }}Copy the code
- How do I choose to load a custom SPI implementation class
The serviceloader.load (xxxx.class) is used to load all instances of the serviceloader.load (xxxx.class) and then order the instances of the serviceloader.load (xxxx.class) and order the instances of the serviceloader.load (xxxx.class). Because the custom SPI implementation priority can be set high, it can override the default implementation
public static <S extends Ordered> S loadPrimary(Class<S> clazz) {
List<S> candidates = loadAllOrdered(clazz);
return candidates.get(0);
}
public static <S extends Ordered> List<S> loadAllOrdered(Class<S> clazz) {
Iterator<S> iterator = loadAll(clazz);
if(! iterator.hasNext()) {throw new IllegalStateException(String.format(
"No implementation defined in /META-INF/services/%s, please check whether the file exists and has the right implementation class!",
clazz.getName()));
}
// Get all SPI implementation instances in the iteration and sort them, taking the one with the highest priority
List<S> candidates = Lists.newArrayList(iterator);
Collections.sort(candidates, new Comparator<S>() {
@Override
public int compare(S o1, S o2) {
// the smaller order has higher priority
returnInteger.compare(o1.getOrder(), o2.getOrder()); }});return candidates;
}
Copy the code
Implementation in JDBC
- The JDBC SPI in the configuration file resources/meta-inf/services/Java, SQL, Driver, and set the parameters as follows
com.example.app.driver.MyDriver
Copy the code
- Inherit SPI interface java.sql.Driver to achieve MyDriver
public class MyDriver extends NonRegisteringDriver implements Driver {
static {
try {
java.sql.DriverManager.registerDriver(new MyDriver());
} catch (SQLException e) {
throw new RuntimeException("Can't register driver!", e); }}public MyDriver(a) throws SQLException {}
@Override
public Connection connect(String url, Properties info) throws SQLException {
System.out.println("MyDriver - Ready to create database connection. Url :" + url);
System.out.println("JDBC configuration Information:" + info);
info.setProperty("user"."root");
Connection connection = super.connect(url, info);
System.out.println("MyDriver - Database connection created!" + connection.toString());
returnconnection; }}Copy the code
- The write main method call executes the custom SPI implementation you just implemented
String url = "jdbc:mysql://localhost:3306/test? serverTimezone=UTC";
String user = "root";
String password = "root";
Class.forName("com.example.app.driver.MyDriver");
Connection connection = DriverManager.getConnection(url, user, password);
Copy the code
- How does JDBC use SPI in a simple analysis
Obtain all registered drivers (each driver is traversed, in fact, is the latest registered one to use that, if failed to find the next)
// Obtain all registered drivers (each driver is iterated, in fact, the latest registered one is used, if the failure to find the next)
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if(con ! =null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return(con); }}catch (SQLException ex) {
if (reason == null) { reason = ex; }}}else {
println(" skipping: "+ aDriver.getClass().getName()); }}Copy the code