JDK SPI 、Spring SPI、Dubbo SPI机制
JDK SPI机制
SPI(Service Provider Interface),是一种将服务接口与服务实现分离以达到解耦可拔插、大大提升了程序可扩展性的机制。
约定(我觉得称之为规范更合适):
1. 制定统一的规范(比如 java.sql.Driver)
2. 服务提供商提供这个规范具体的实现,在自己jar包的META-INF/services/目录里创建一个以服务接口命名的文件,内容是实现类的全命名(比如:com.mysql.jdbc.Driver)。
3. 平台引入外部模块的时候,就能通过该jar包META-INF/services/目录下的配置文件找到该规范具体的实现类名,然后装载实例化,完成该模块的注入。
这个机制最大的优点就是无须在代码里指定,进而避免了代码污染,实现了模块的可拔插。
JDK SPI的一个典型案例就是 java.sql.Driver 了
我们最熟悉的代码
// 加载驱动 Class.forName("com.mysql.jdbc.Driver"); // 获取连接 Connection connection = DriverManager.getConnection("url", "user", "password");
我们进入DriverManager类,里面有个静态代码块,这部分的内容会先执行。
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
加载并初始化驱动
private static void loadInitialDrivers() { // ... // 如果驱动被打包作为服务提供者,则加载它。 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // 1. load ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 2. 获取Loader的迭代器 Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ // 3. 调用next方法 while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); // ... }
看 ServiceLoader#load方法
public static <S> ServiceLoader<S> load(Class<S> service) { // 获取类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); // load return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){ // new 一个 ServiceLoader,参数为服务接口Class和类加载器 return new ServiceLoader<>(service, loader); }
可以看出,上面主要是获取类加载器并新建ServiceLoader的过程,没有加载实现类的动作。现在看:ServiceLoader#iterator 方法
public Iterator<S> iterator() { // 这里是个Iterator的匿名内部类,重写了一些方法 return new Iterator<S>() { // 已存在的提供者 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { // 先检查缓存 if (knownProviders.hasNext()) return true; // 缓存没有,走 java.util.ServiceLoader.LazyIterator#hasNext 方法 return lookupIterator.hasNext(); } public S next() { // 同样先检查缓存 if (knownProviders.hasNext()) return knownProviders.next().getValue(); // 缓存没有,走 java.util.ServiceLoader.LazyIterator#next 方法 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
看 ServiceLoader.LazyIterator#hasNext 方法
// java.util.ServiceLoader.LazyIterator#hasNext public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // 获取全路径:META-INF/services/java.sql.Driver 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); } } // 这里负责解析前面加载的配置信息 while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } // 解析的返回值是一个 Iterator<String> 类型,其中的String代表文件里配置的实现类全限定名,比如:com.mysql.jdbc.Driver pending = parse(service, configs.nextElement()); } // 把nextName初始化 nextName = pending.next(); return true; }
看 ServiceLoader.LazyIterator#next 方法
// java.util.ServiceLoader.LazyIterator#next public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } // 进入 java.util.ServiceLoader.LazyIterator#nextService 方法 private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); // com.mysql.jdbc.Driver String cn = nextName; nextName = null; Class<?> c = null; try { // 加载 com.mysql.jdbc.Driver Class c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { // 实例化并转换成 com.mysql.jdbc.Driver 对象 S p = service.cast(c.newInstance()); // 添加到缓存 providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
所以,DriverManager做了什么事:
// 1. 获取类加载器并创建ServiceLoader对象 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); // 2. 创建迭代器并覆盖hasNext和next方法 Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ // 3. 这个方法主要是读取配置文件,获取其中实现类的全限定名。 while(driversIterator.hasNext()) { // 4. 这个方法主要是根据全限定名去生成一个实例 driversIterator.next(); } } catch(Throwable t) { // Do nothing }
另外你也能发现,ServiceLoader只提供了遍历的方式来获取目标实现类,没有提供按需加载的方法,这也是常说的不足之处。
自己实现
定义一个接口
package com.demo.spi; public interface Funny { void deal(); }
写一个实现类
package com.demo.spi; public class FunnyImpl implements Funny { @Override public void deal() { System.out.println("FunnyImpl"); } }
配置
文件内容
执行测试
@Test public void test(){ ServiceLoader<Funny> serviceLoader = ServiceLoader.load(Funny.class); serviceLoader.forEach(Funny::deal); }
输出
Spring SPI机制
对于Spring的SPI机制主要体现在SpringBoot中。我们知道SpringBoot的启动包含new SpringApplication和执行run方法两个过程,new的时候有这么个逻辑:(getSpringFactoriesInstances)
这个方法走到里面,无非 1. 加载类的全限定名列表。2. 根据类名通过反射实例化。
重点在于:SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // 获取类的全限定名 String factoryClassName = factoryClass.getName(); // 1. 执行loadSpringFactories,这里只传入了类加载器,肯定是要获取全部配置 // 2. getOrDefault,获取指定接口的实现类名称列表,如果没有则返回一个空列表 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 先检查缓存 MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 获取类路径下所有META-INF/spring.factories Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // 把加载的配置转换成Map<String, List<String>>格式 while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
它还提供了实例化的方法:SpringFactoriesLoader.loadFactories(factoryClass, classLoader) 利用反射实现,理解起来也不困难,不做解释了
对比ServiceLoader:
1. 都是XXXLoader。命名格式都一样。
2. 一个是加载 META-INF/services/ 目录下的配置;一个是加载 META-INF/spring.factories 固定文件的配置。思路都一样。
3. 两个都是利用ClassLoader和ClassName来完成操作的。不同的是Java的ServiceLoader加载配置和实例化都是自己来实现,并且不能按需加载;SpringFactoriesLoader既可以单独加载配置然后按需实例化也可以实例化全部。
如何实现一个Spring-Boot-Starter?
先看一下SpringBoot的相关要点。
这个是SpringBoot的 @SpringBootApplication 注解,里面还有一个 @EnableAutoConfiguration 注解,开启自动配置的。
可以发现这个自动配置注解在另一个工程,而这个工程里也有个spring.factories文件,如下图:
我们知道在SpringBoot的目录下有个spring.factories文件,里面都是一些接口的具体实现类,可以由SpringFactoriesLoader加载。
那么同样的道理,spring-boot-autoconfigure模块也能动态加载了。看一下其中的内容:
我们看到有个接口【org.springframework.boot.autoconfigure.EnableAutoConfiguration】,有N个配置类,都是自动配置相关的,那么我们可以猜一猜,是不是模仿一下这样的配置,我们就可以丝滑进入了?
为了证明这一点,我们看一下 dubbo-spring-boot-starter 的结构
你会发现starter项目没有实现类,只有个pom文件。
它的关键在于引入了一个 autoconfigure 依赖。
这个里面配置了Spring的 spring.factories 其中只有一个配置
看到这里你是不是感觉到了什么:
1. 新建一个只有pom的starter工程,引入写好的自动配置模块。
2. 自动配置模块配置 spring.factories 文件,格式如上。
3. 具体实现自动配置逻辑。
这样当SpringBoot启动加载配置类的时候,就会把这些第三方的配置一起加载。
所以我认为 Starter的作用就是在启动之前把第三方的配置加载到容器中,具体表现就是无须用户手动配置即可使用。【如果不准确望指正】
具体实现一个starter
两个maven工程,目录如下:
custom-spring-boot-autoconfigure↓↓↓↓↓
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.1.6.RELEASE</version> </dependency> </dependencies> </project>
CustomAutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") public class CustomAutoConfiguration { @Bean public CustomConfig customConfig(){ System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!第三方自定义的配置"); return new CustomConfig(); } }
CustomConfig
/** * 自定义配置 */ public class CustomConfig { private String value = "Default"; }
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.CustomAutoConfiguration
一共四个文件
custom-spring-boot-starter↓↓↓↓
只有pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
最后把这两个工程install
下面是新建一个SpringBoot项目,然后引入咱们自定义的starter依赖。
<dependency> <groupId>com.demo</groupId> <artifactId>custom-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
然后启动就可以啦,可以看见,用户引入依赖之后无需手动配置就可以使用第三方插件
看下工程依赖
Dubbo SPI机制
上面介绍了两种SPI,到Dubbo这里就不难理解了吧?
思路都是处理类+约定配置 ,在Dubbo里,约定扩展配置在 META-INF/dubbo/internal/ 目录下
在Dubbo源码里也可以发现,核心类是 ExtensionLoader
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
getExtensionLoader
// 这个方法就是获取一个特定类型的ExtensionLoader的实例 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 先从缓存中取 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { // 缓存没有则新建 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
下面看getExtension方法
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { // 获取默认的扩展类 return getDefaultExtension(); } // Holder用来持有目标对象 final Holder<Object> holder = getOrCreateHolder(name); // 获取实例 Object instance = holder.get(); // 没有的话,双重检查并创建实例 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
看createExtension方法
private T createExtension(String name) { // 1. 获取扩展Class Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { // 先从缓存中获取 T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 没有则新建 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 2. IOC注入依赖 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
1. 获取所有扩展类
private Map<String, Class<?>> getExtensionClasses() { // 缓存 + 双重检查 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 加载扩展类 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } // 对于loadDirectory,每个目录有两个执行逻辑,因为目前要兼容alibaba老版本 private Map<String, Class<?>> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 加载目录:META-INF/dubbo/internal/ loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); // 加载目录:META-INF/dubbo/ loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); // 加载目录:META-INF/services/ loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; } private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) { // 文件路径,比如 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol String fileName = dir + type; try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); // 加载文件 if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); // 加载资源(读取文件内容) loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } } private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; // 按行读取 while ((line = reader.readLine()) != null) { // 判断注释 final int ci = line.indexOf('#'); if (ci >= 0) { // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略 line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; // 按照 = 分割,前面是name,后面是类全限定名 int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加载Class loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } // 检测目标类上是否有 Adaptive 注解 if (clazz.isAnnotationPresent(Adaptive.class)) { // 设置 cachedAdaptiveClass缓存 cacheAdaptiveClass(clazz); // 检测 clazz 是否是 Wrapper 类型 } else if (isWrapperClass(clazz)) { // 存储 clazz 到 cachedWrapperClasses 缓存中 cacheWrapperClass(clazz); } else { // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常 clazz.getConstructor(); if (StringUtils.isEmpty(name)) { // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { // 存储 name 到 Activate 注解对象的映射关系 cacheActivateClass(clazz, names[0]); for (String n : names) { // 存储 Class 到名称的映射关系 cacheName(clazz, n); // 存储名称到 Class 的映射关系 saveInExtensionClass(extensionClasses, clazz, n); } } } }
这部分有些注释直接拿的官网,总结下来这部分:
1. 根据约定的路径比如【META-INF/dubbo/internal/】加载文件URl。
2. 读取并解析每一个文件内容,具体为按行读取,去掉注释,按等号分割为 name-class全限定名键值对。
3. 根据类全限定名生成Class对象,根据Class对象的的特点进行相关的缓存以及name到Class对象的缓存。
2. IOC注入依赖
private T injectExtension(T instance) { try { if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { // 这里判断方法是否是setter方法,Dubbo目前只处理setter的IOC if (isSetter(method)) { // 如果标注了@DisableInject,则不进行注入 if (method.getAnnotation(DisableInject.class) != null) { continue; } // 获取 setter 方法参数类型 Class<?> pt = method.getParameterTypes()[0]; // 基本类型跳过 if (ReflectUtils.isPrimitives(pt)) { continue; } try { // 获取属性名,比如 setName 方法对应属性名 name String property = getSetterProperty(method); // 从 ObjectFactory 中获取依赖对象 Object object = objectFactory.getExtension(pt, property); if (object != null) { // 通过反射调用 setter 方法设置object依赖 method.invoke(instance, object); } } catch (Exception e) { logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
这部分容易理解,核心部分利用反射实现,其它前置处理做了一些校验。
而使用Dubbo SPI的话也是一样的套路,扩展类实现 + 配置 + ExtensionLoader