Java SPI机制
Java SPI机制
什么是SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
SPI有什么用
调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
如:
-
数据库加载驱动
java.sql.DriverManager
AccessController.doPrivileged(new PrivilegedAction<Void>() { public void run() { //此处使用JDK的SPI加载机制,加载所有的驱动,即java.sql.Driver接口的实现类 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);
-
Dubbo
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface SPI { /** * default extension name */ String value() default ""; }
SPI工作机制
代码片段 java.util.ServiceLoader
/**
* Creates a new service loader for the given service type, using the
* current thread's {@linkplain java.lang.Thread#getContextClassLoader
* context class loader}.
*
* <p> An invocation of this convenience method of the form
*
* <blockquote><pre>
* ServiceLoader.load(<i>service</i>)</pre></blockquote>
*
* is equivalent to
*
* <blockquote><pre>
* ServiceLoader.load(<i>service</i>,
* Thread.currentThread().getContextClassLoader())</pre></blockquote>
*
* @param <S> the class of the service type
*
* @param service
* The interface or abstract class representing the service
*
* @return A new service loader
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
通过调用load方法来实现对象的创建
最终使用Class.forName("class name").newInstance()
来创建接口实现
SPI使用
-
当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
-
接口实现类所在的jar包放在主程序的classpath中;
-
主程序通过ServiceLoader.load()动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
-
SPI的实现类必须携带一个不带参数的构造方法;
参考: