Dubbo之SPI机制
Dubbo版本
- Dubbo版本2.6.7
JDK SPI
介绍
SPI(Service provider interface的缩写)是JDK内置的一种服务提供发现机制,符合对修改封闭,对扩展开放的原则- 作用: 基于某种约定为接口查找相应的实现类(一个或者多个),主要是针对不同的服务提供厂商,对不同场景的提供不同的解决方案制定的一套标准,比如JDBC驱动
- jdk提供服务实现查找的一个工具类:
java.util.ServiceLoader
使用方式
-
当服务的提供者,提供了服务接口的一种实现之后,在jar包的
META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 -
创建一个
META-INF/services/目录,并放到ClassPath目录下 -
关于
META-INF/services的文件格式:- 文件名字必须是SPI接口类的全名
- 文件的内容是SPI服务具体实现类的全限定名,如果有多个则使用分隔符分隔
- 文件采用UTF-8编码,用
#来表示注释 - 关于SPI接口以及实现类:实现类必须要有一个无参的构造方法,ServiceLoader支持懒加载方式,只有当需要的时候,才去加载服务的实现类。
-
调用:
ServiceLoader.load(xxxx.class); -
jdbc4.0以前,开发人员还需要基于
Class.forName("xxx")的方式来装载驱动,jdbc4也基于spi的机制来发现驱动提供商了,可以通过META-INF/services/java.sql.Driver文件里指定实现类的方式来暴露驱动提供者。比如mysql-connector-java-8.0.19.jar// 查看文件META-INF/services/java.sql.Driver com.mysql.cj.jdbc.Driver //java.sql.DriverManager有一个静态代码块 static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { ...省略... ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); ...省略... }
案例
-
定义接口和实现类
1. 定义接口 public interface BusinessInterface { void dosomething(String username); } 2. 实现类 public class RealBusinessImpl implements BusinessInterface { @Override public void dosomething(String username) { System.out.println("正在为用户:" + username + ",进行真实的业务处理。。。"); } } -
配置:创建
META-INF/services目录,在此目录下创建以接口全限定名为名称的文件cn.jannal.spi.BusinessInterface在此文件中写具体实现的全限定名cn.jannal.spi.impl.RealBusinessImpl -
调用
public class Main { public static void main(String[] args) throws RuntimeException { //普通创建对象方式 nospi(); //spi方式加载 spi(); } private static void nospi() { BusinessInterface realBusiness = new RealBusinessImpl(); realBusiness.dosomething("普通方式"); } private static void spi() { ServiceLoader<BusinessInterface> businessInterface = ServiceLoader.load(BusinessInterface.class); //获取所有的实现,具体调用哪个由自己决定 Iterator<BusinessInterface> iinterface = businessInterface.iterator(); if (iinterface.hasNext()) { BusinessInterface interfaceItem = iinterface.next(); interfaceItem.dosomething("spi方式"); } } } -
运行结果
正在为用户:普通方式,进行真实的业务处理。。。 正在为用户:spi方式,进行真实的业务处理。。。
源码分析
-
ServiceLoader核心源码分析
public final class ServiceLoader<S> implements Iterable<S> { //查找配置文件的目录,这里可以看到,使用默认的方式加载,这个目录是写死的,ServiceLoader在load时提供Classloader,则可以从其他的目录读取。 private static final String PREFIX = "META-INF/services/"; // 要被加载的服务类或接口 private final Class<S> service; // 这个ClassLoader用来定位,加载,实例化服务提供者 private final ClassLoader loader; //访问控制 private final AccessControlContext acc; //缓存已经被实例化的服务提供者,按照实例化的顺序存储 <接口全名称, 实现类实例> private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 懒加载查找迭代器(真实的迭代器),不对外暴露,外层迭代器委托此迭代器进行操作 private LazyIterator lookupIterator; /** * 重新加载,就相当于重新创建ServiceLoader了,用于新的服务提供者安装到正在运行的Java虚拟机中的情况。 */ public void reload() { //清空缓存中所有的服务提供者(实现类) providers.clear(); //创建一个新的迭代器 lookupIterator = new LazyIterator(service, loader); } /** * 使用指定的类加载器和服务实现类,如果没有指定加载器,就是应用的 */ private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); } private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg); } private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ":" + line + ": " + msg); } /* * 解析配置文件的一行, */ private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { String ln = r.readLine(); if (ln == null) { return -1; } int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; } private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, "Error reading configuration file", x); } finally { try { if (r != null) r.close(); if (in != null) in.close(); } catch (IOException y) { fail(service, "Error closing configuration file", y); } } return names.iterator(); } // Private inner class implementing fully-lazy provider lookup // private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; //实现类的URL路径 Enumeration<URL> configs = null; //实现类的全名 Iterator<String> pending = null; //下一个实现类的全名 String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { 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; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { 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 { 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 } 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); } } 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); } } public void remove() { throw new UnsupportedOperationException(); } } /** * 以懒加载的方式加载可用的服务提供者 * 懒加载的实现是:解析配置文件和实例化服务提供者的工作由迭代器本身完成 **/ public Iterator<S> iterator() { return new Iterator<S>() { //按照实例化顺序返回已经缓存的服务提供者实例 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } public String toString() { return "java.util.ServiceLoader[" + service.getName() + "]"; } } -
执行顺序
ServiceLoader.load(Xxxx.class) 获取当前类的类加载器 --> ServiceLoader(Class<S> svc, ClassLoader cl) --> reload() : 清空服务提供者,并且创建一个懒加载迭代器 Iterator<XxxxInterface> iinterface = XxxxInterface.iterator() --> 返回一个迭代器(委托LazyIterator提供逻辑) iinterface.hasNext() --> lookupIterator.hasNext() --> hasNextService() --> classLoader.getResources(fullName) //从META-INF/services/加载文件 --> parse(Class<?> service, URL u) // 解析文件 --> new BufferedReader(new InputStreamReader(in, "utf-8")); //字符编码必须是utf-8 --> parseLine() //解析一行 iinterface.next() --> nextService() //这里可以看到,服务实现类必须提供无参数的构造方法,否则就无法实例化 --> S p = service.cast(c.newInstance()) -
从上面可以看出,在调用
load方法时并没有加载配置文件和实例化服务实现类,而是在调用迭代器的hasNext()时才去加载文件并解析。所有的配置文件只会加载一次,服务提供者也只会被实例化一次,重新加载配置文件可使用reload方法(第一次加载其实也是调用了reload)
缺点
- JDK标准的SPI会一次性实例化扩展点所有实现(遍历所有实现并实例化),如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
- 从源码来看,不是线程安全的
Dubbo SPI
-
Dubbo 改进了 JDK 标准的 SPI 的以下问题
-
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,即使没用也会加载,很浪费资源。Dubbo SPI只是加载配置文件中的类,并分成不同的种类缓存在内存中,不会立即全部初始化
-
如果扩展点加载失败,连扩展点的名称都拿不到,异常信息可能丢失,追踪问题比较困难。Dubbo SPI在扩展加载失败的时候会先抛出真实异常并打印日志。扩展点在被加载时,及时有部分扩展加载失败也不会影响其他扩展点和整个框架的使用
-
增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点
-
可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
-
对于扩展实现IOC依赖注入功能:举例来说:接口A,实现者A1、A2。接口B,实现者B1、B2。现在实现者A1含有setB()方法,会自动注入一个接口B的实现者,此时注入B1还是B2呢?都不是,而是注入一个动态生成的接口B的实现者
B$Adpative,该实现者能够根据参数的不同,自动引用B1或者B2来完成相应的功能 -
兼容JDK SPI
-
-
Dubbo默认扫描
META-INF/dubbo/internal/META-INF/service/META-INF/dubbo/
-
配置规范
- 配置文件名称是接口全限定名
- 配置目录
META-INF/dubbo/internal/META-INF/service/META-INF/dubbo/ - 文件内容格式,key=value,多个使用换行分隔
使用方式
-
添加依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-common</artifactId> <version>${project.version}</version> </dependency> -
定义接口和实现类,并为接口添加
@SPI注解,设置默认实现为real1. 定义接口 package cn.jannal.spi; @SPI("real") public interface BusinessInterface { void dosomething(String username); } 2. 实现类 package cn.jannal.spi.impl; public class RealBusinessImpl implements BusinessInterface { @Override public void dosomething(String username) { System.out.println("正在为用户:" + username + ",进行真实的业务处理。。。"); } } -
步骤
- 在目录
META-INF/dubbo/internal下建立配置文件,名称为接口全限定名称cn.jannal.spi.BusinessInterface - 文件内容为
real=cn.jannal.spi.impl.RealBusinessImpl,即配置名=扩展实现类全限定名,多个实现类用换行符分隔 - 使用
ExtensionLoader(相当于JDK中的ServiceLoader)
- 在目录
-
调用
public class Main { public static void main(String[] args) { ExtensionLoader<BusinessInterface> extensionLoader = ExtensionLoader.getExtensionLoader(BusinessInterface.class); BusinessInterface businessInterface = extensionLoader.getDefaultExtension(); //正在为用户:jannal,进行真实的业务处理。。。 businessInterface.dosomething("jannal"); //通过配置名称获取实现 final BusinessInterface real = extensionLoader.getExtension("real"); //正在为用户:jack,进行真实的业务处理。。。 real.dosomething("jack"); } }
浙公网安备 33010602011771号