2.5 Dubbo增强SPI
前面我们讲解了Dubbo框架如何使用动态编译技术给每个扩展接口生成适配器类,并讲解了适配器类根据其中的参数来选择对应的SPI实现,下面我们讲解在适配器类中是如何根据参数来装载具体的SPI实现的。
2.5.1 JDK标准SPI
Dubbo增强的SPI功能是从JDK标准SPI演化而来的,所以有必要先讲讲标准SPI的原理。
JDK中的SPI是面向接口编程的,服务规则提供者会在JRE的核心API里提供服务访问接口,而具体实现则由其他开发商提供。
例如,如果规范制定者在rt.jar包里定义了数据库的驱动接口java.sql.Driver,那么MySQL实现的开发商则会在MySQL的驱动包的META-INF/services文件夹下建立名称为java.sql.Driver的文件,文件内容就是MySQL对java.sql.Driver接口的实现类,如图2.4所示。
图2.4
通过如下代码可知,com.mysql.jdbc.Driver实现了java.sql.Driver接口:
上面讲解了如何使用SPI扩展自定义自己的实现,下面说说SPI的实现原理。我们知道Java核心API(比如rt.jar包)是使用Bootstrap ClassLoader类加载器加载的,而用户提供的Jar包是由AppClassLoader加载的。如果一个类由类加载器加载,那么这个类依赖的类也是由相同的类加载器加载的。
用来搜索开发商提供的SPI扩展实现类的API类(ServiceLoader)是使用Bootstrap ClassLoader加载的,那么ServiceLoader里面依赖的类应该也是由Bootstrap ClassLoader加载的。而上面说了用户提供的包含SPI实现类的Jar包是由AppClassLoader加载的,所以这就需要一种违反双亲委派模型的方法,线程上下文类加载器ContextClassLoader就是用来解决这个问题的。
下面我们写一段测试代码,看看具体是如何工作的。
然后引入MySQL驱动的Jar包,执行结果如下。
从结果可知找到了MySQL的驱动,如果在引入Oracle数据库驱动的Jar包后再运行,则输出会显示找到了MySQL和Oracle的驱动,这也说明了JDK标准的SPI会同时把SPI接口的所有实现类提前加载好实例:
另外,从执行结果可以知道ServiceLoader的加载器为Bootstarp,因为这里输出了null,并且从该类在rt.jar里面这一点也可以证明。
下面我们看看ServiceLoader的load()方法源码。
代码5获取了当前线程上下文加载器,这里是AppClassLoader。
代码6将该类加载器传递给新构造的ServiceLoader的成员变量loader。那么这个loader在什么时候使用呢?下面我们看看LazyIterator的next()方法。
代码7使用loader也就是AppClassLoader加载具体的驱动实现类的Class对象,代码8则使用Class对象调用newInstance()方法来创建对象实例。至于cn是怎么来的,读者可以参见LazyIterator的hasNext()方法:
2.5.2 增强SPI原理
Dubbo的扩展点加载机制是基于JDK标准的SPI扩展机制增强而来的,Dubbo解决了JDK标准的SPI的以下问题:
· JDK标准的SPI会一次性实例化扩展点的所有实现,如果有些扩展实现初始化很耗时,但又没用上,那么加载就很浪费资源。
· 如果扩展点加载失败,是不会友好地向用户通知具体异常的。比如:对于JDK标准的ScriptEngine来说,如果Ruby ScriptEngine因为所依赖的jruby.jar不存在,导致Ruby ScriptEngine类加载失败,那么这个失败原因就被隐藏了,当用户执行Ruby脚本时,会报空指针异常,而不是报Ruby ScriptEngine不存在。
· 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接使用setter()方法注入其他扩展点,也可以对扩展点使用Wrapper类进行功能增强。
本节我们结合服务提供者配置类ServiceConfig来讲解如何使用增强SPI加载扩展接口Protocol的实现类,在ServiceConfig类中,有如下代码:
这里的ExtensionLoader类似JDK标准SPI里的ServiceLoader类,代码ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()的作用是获取Protocol接口的适配器类,在Dubbo中每个扩展接口都有一个对应的适配器类,前面所述这个适配器类是动态生成的一个类,这里我们给出Protocol扩展接口对应的适配器类的代码:
所以当我们调用protocol.export(invoker)方法的时候实际调用的是动态生成的Protocol$Adaptive实例的export(invoker)方法,其内部代码1首先获取参数里的URL对象,然后从URL对象里获取用户设置的协议(Protocol)的实现类的名称,然后调用代码2根据名称获取具体的Protocol协议的实现类(后面我们会知道获取的是被使用Wrapper类增强后的实现类),最后代码3具体调用Protocol协议的实现类的export(invoker)方法。
下面我们结合时序图(见图2.5)来讲解ExtensionLoader的getAdaptiveExtension()方法是如何动态生成扩展接口对应的适配器类,以及getExtension()方法如何根据扩展实现类的名称找到对应的扩展实现类的。
图2.5
图2.5中的步骤1获取当前扩展接口对应的ExtensionLoader对象,在Dubbo中每个扩展接口对应着自己的ExtensionLoader对象,如下面的代码所示,内部通过并发Map来缓存扩展接口与对应的ExtensionLoader的映射,其中key为扩展接口的Class对象,value为对应的ExtensionLoader实例:
从上面的代码可以看到,第一次访问某个扩展接口时需要新建一个对应的ExtensionLoader并放入缓存,后面就直接从缓存中获取。
步骤2获取当前扩展接口对应的适配器对象,getAdaptiveExtension()方法的代码如下:
上面的代码通过双重锁检查创建cachedAdaptiveInstance对象,接口对应的适配器对象就保存到这个对象里。
我们重点看看步骤3的createAdaptiveExtension()方法,因为具体创建适配器对象的是这个方法。createAdaptiveExtension()方法的代码如下:
这里首先调用了步骤4的getAdaptiveExtensionClass().newInstance()以获取适配器对象的一个实例,然后调用步骤7的injectExtension()方法进行扩展点相互依赖注入(扩展点之间的依赖自动注入)。下面首先看看步骤4的getAdaptiveExtensionClass()方法是如何动态生成适配器类的Class对象的:
上面的代码首先调用了步骤5的getExtensionClasses()方法获取了该扩展接口所有实现类的Class对象,然后调用了步骤6的createAdaptiveExtensionClass()方法创建具体的适配器对象的Class对象。前面我们讲解过createAdaptiveExtensionClass()方法,该方法根据字符串代码生成适配器的Class对象并返回,然后通过getAdaptiveExtensionClass().newInstance()创建适配器类的一个对象实例。至此扩展接口的适配器对象已经创建完毕。
下面,我们在看步骤7之前,先看看步骤5的getExtensionClasses()方法是如何加载扩展接口的所有实现类的Class对象的。其内部最终调用了loadExtensionClasses()方法进行加载,loadExtensionClasses()方法的代码如下:
拿Protocol协议来说,这里SPI被注解为@SPI("dubbo"),这里的cachedDefaultName就是dubbo。然后,loadDirectory()方法到META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/目录下去加载具体的扩展实现类,比如Protocol协议默认实现类为DubboProtocol,如图2.6所示。
图2.6
步骤7的injectExtension()方法进行扩展点实现类相互依赖自动注入(IoC功能):
至此,ExtensionLoader的getAdaptiveExtension()方法是如何动态生成扩展接口对应的适配器类以及如何加载扩展接口的实现类的Class对象的就讲解完了,下面我们看看getExtension()方法是如何根据扩展实现类的名称找到对应的实现类的:
其中createExtension()方法的代码如下:
至此,如何使用getExtension(String name)方法加载具体的扩展实现类也讲完了。上面我们讲解的getExtension(String name)方法只会加载某一个扩展接口实现的Class对象的实例,但在有些情况下我们需要全部创建,比如ProtocolFilterWrapper类中的buildInvokerChain()方法在建立Filter责任链时,需要把属于某一个group的所有Filter都放到责任链里,其是通过如下方式来获取属于某个组的Filter扩展实现类的:
比如,当服务提供端启动时只会加载group为provider的Filter扩展实现类:
当消费端启动时只会加载group为consumer的Filter扩展实现类:
另外还需要注意,并不是所有属于某个group的Filter都会被加载,还需要看其设置的value的值是否在URL里(用户是否设置了该value的属性),比如ActiveLimitFilter在默认情况下是不会在服务消费端加载到Filter链的,只有当消费端设置了并发活跃数actives属性时才会(设置后actives属性就会出现在URL里了),具体内容可以参见9.1节。这里我们以加载Filter为例看看getActivateExtension()方法的实现原理:
代码1首先获取接口org.apache.dubbo.rpc.Filter的所有扩展实现,然后遍历所有扩展接口实现;代码2.2判断当前扩展实现所属分组是不是我们需要的group,如果是则还要看扩展接口实现的注解中的value值是否在URL中(这是isActive()方法所做的事情),如下列代码所示:
上面的代码遍历当前扩展实现的value值,如果发现某一个值在URL中,则返回true,否则返回false。
2.5.3 扩展点的自动包装
在Spring AOP中,我们可以使用多个切面对指定类的方法进行增强,在Dubbo中也提供了类似的功能。在Dubbo中你可以指定多个Wrapper类对指定的扩展点的实现类的方法进行增强。
如下面的代码所示,当执行protocol.export(wrapperInvoker)方法时,实际调用的是适配器Protocol$Adaptive的export()方法,如果URL对象里面的protocol为dubbo,那么在没有扩展点自动包装时,protocol.export()方法返回的就是DubboProtocol的对象。
而在真正的情况下,Dubbo里使用ProtocolFilterWrapper、ProtocolListenerWrapper等Wrapper类对DubboProtocol对象进行包装增强。
ProtocolFilterWrapper、ProtocolListenerWrapper、DubboProtocol三个类都有一个拷贝构造函数,这个拷贝构造函数的参数就是扩展接口Protocol。所谓包装,其含义如下:
比如这里会进行两次包装,第一次首先使用ProtocolListenerWrapper类对DubboProtocol进行包装,这时ProtocolListenerWrapper类里的impl就是DubboProtocol,然后第二次使用ProtocolFilterWrapper对ProtocolListenerWrapper进行包装,也就是说ProtocolFilterWrapper里的impl是ProtocolListenerWrapper,这时会调用适配器Protocol$Adaptive的export()方法,如果URL对象里面的protocol为dubbo,那么在扩展点自动包装时,protocol.export返回的就是ProtocolFilterWrapper的实例了。
下面我们看看在Dubbo增强的SPI中如何去收集这些包装类,以及使用包装类对SPI实现类的自动包装(AOP功能)是如何实现的。
其实,上一节所讲的getExtensionClasses里面的loadDirectory()方法除了加载扩展接口的所有实现类的Class对象,还对包装类(Wrapper类)进行了收集,下面我们看看loadDirectory->loadResource中的loadClass()方法的代码:
上面的代码使用isWrapperClass()方法方法判断clazz是否为Wrapper类,其中调用了clazz.getConstructor(type)方法来判断SPI实现类clazz是否有参数类型为type的构造函数。如果没有,则直接抛出异常NoSuchMethodException,该异常被catch块捕获了,然后返回false;如果有,则说明clazz类为Wrapper类,此时返回true并调用cacheWrapperClass()方法将Wrapper类的Class对象收集起来放入cachedWrapperClasses集合。至此,Wrapper类收集完毕。
而对扩展实现类使用收集的Wrapper类进行自动包装是在createExtension()方法里完成的:
上面的代码遍历了所有Wrapper类,并使用injectExtension一层层对扩展实现类进行功能增强。