Dubbo如何通過(guò)SPI提高框架的可擴(kuò)展性?
介紹
最近看了一下Dubbo的源碼,國(guó)人寫(xiě)的框架和國(guó)外的果然是兩種不同的風(fēng)格,Dubbo的源碼還是比較清晰容易懂的。Spring框架一個(gè)Bean的初始化過(guò)程就能繞死在源碼中.
Dubbo的架構(gòu)是基于分層來(lái)設(shè)計(jì)的,每層執(zhí)行固定的功能,上層依賴(lài)下層,下層的改變對(duì)上層不可見(jiàn),每層都是可以被替換的組件
Service和Config為API接口層,讓Dubbo使用者方便的發(fā)布和引用服務(wù)
其他各層均為SPI層,意味著每層都是組件化的,可以被替換
例如,注冊(cè)中心可以用Redis,Zookeeper。傳輸協(xié)議可以用dubbo,rmi,hessian等。
網(wǎng)絡(luò)通信可以用mina,netty。序列化可以用fastjson,hessian2,java原生的方式等
SPI 全稱(chēng)為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。SPI 的本質(zhì)是將接口實(shí)現(xiàn)類(lèi)的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實(shí)現(xiàn)類(lèi)。這樣可以在運(yùn)行時(shí),動(dòng)態(tài)為接口替換實(shí)現(xiàn)類(lèi)。正因此特性,我們可以很容易的通過(guò) SPI 機(jī)制為我們的程序提供拓展功能
那么Dubbo的SPI是怎么實(shí)現(xiàn)的呢?先來(lái)了解一下Java SPI
Java SPI
Java SPI是通過(guò)策略模式實(shí)現(xiàn)的,一個(gè)接口提供多個(gè)實(shí)現(xiàn)類(lèi),而使用哪個(gè)實(shí)現(xiàn)類(lèi)不在程序中確定,而是配置文件配置的,具體步驟如下
- 定義接口及其對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)
- 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件
- 文件內(nèi)容為實(shí)現(xiàn)類(lèi)的全路徑名
- 在代碼中通過(guò)java.util.ServiceLoader#load加載具體的實(shí)現(xiàn)類(lèi)
寫(xiě)個(gè)Demo演示一下
- public interface Car {
- void getBrand();
- }
- public class BenzCar implements Car {
- @Override
- public void getBrand() {
- System.out.println("benz");
- }
- }
- public class BMWCar implements Car {
- @Override
- public void getBrand() {
- System.out.println("bmw");
- }
- }
org.apache.dubbo.Car的內(nèi)容如下
- org.apache.dubbo.BenzCar
- org.apache.dubbo.BMWCar
測(cè)試類(lèi)
- public class JavaSpiDemo {
- public static void main(String[] args) {
- ServiceLoader<Car> carServiceLoader = ServiceLoader.load(Car.class);
- // benz
- // bmw
- carServiceLoader.forEach(Car::getBrand);
- }
- }
Dubbo SPI
用Dubbo SPI將上面的例子改造一下
- @SPI
- public interface Car {
- void getBrand();
- }
- public class BenzCar implements Car {
- @Override
- public void getBrand() {
- System.out.println("benz");
- }
- }
- public class BMWCar implements Car {
- @Override
- public void getBrand() {
- System.out.println("bmw");
- }
- }
org.apache.dubbo.quickstart.Car的內(nèi)容如下
- benz=org.apache.dubbo.quickstart.BenzCar
- bmw=org.apache.dubbo.quickstart.BMWCar
測(cè)試類(lèi)
- public class DubboSpiDemo {
- public static void main(String[] args) {
- ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
- Car car = extensionLoader.getExtension("benz");
- car.getBrand();
- }
- }
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE})
- public @interface SPI {
- String value() default "";
- }
@SPI標(biāo)記接口是一個(gè)Dubbo SPI接口,即是一個(gè)擴(kuò)展點(diǎn),value屬性可以指定默認(rèn)實(shí)現(xiàn)
Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對(duì)其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。Dubbo SPI的優(yōu)點(diǎn)如下
- JDK標(biāo)準(zhǔn)的SPI會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)。而Dubbo SPI能實(shí)現(xiàn)按需加載
- Dubbo SPI增加了對(duì)擴(kuò)展點(diǎn)Ioc和Aop的支持
Dubbo SPI的實(shí)現(xiàn)步驟如下
- 定義接口及其對(duì)應(yīng)的實(shí)現(xiàn)類(lèi),接口上加@SPI注解,表明這是一個(gè)擴(kuò)展類(lèi)
- 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件
- 文件內(nèi)容為實(shí)現(xiàn)類(lèi)的全路徑名
- 在代碼中通過(guò)ExtensionLoader加載具體的實(shí)現(xiàn)類(lèi)
Dubbo SPI 擴(kuò)展點(diǎn)的特性自動(dòng)包裝
擴(kuò)展類(lèi)的構(gòu)造函數(shù)是一個(gè)擴(kuò)展點(diǎn),則認(rèn)為這個(gè)類(lèi)是一個(gè)Wrapper類(lèi),即AOP
用例子演示一下
- @SPI
- public interface Car {
- void getBrand();
- }
- public class BenzCar implements Car {
- @Override
- public void getBrand() {
- System.out.println("benz");
- }
- }
- public class CarWrapper implements Car {
- private Car car;
- public CarWrapper(Car car) {
- this.car = car;
- }
- @Override
- public void getBrand() {
- System.out.println("start");
- car.getBrand();
- System.out.println("end");
- }
- }
org.apache.dubbo.aop.Car內(nèi)容如下(resources\META-INF\services目錄下)
- benz=org.apache.dubbo.aop.BenzCar
- org.apache.dubbo.aop.CarWrapper
測(cè)試類(lèi)
- public class DubboSpiAopDemo {
- public static void main(String[] args) {
- ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
- Car car = extensionLoader.getExtension("benz");
- // start
- // benz
- // end
- car.getBrand();
- }
- }
BenzCar是一個(gè)擴(kuò)展類(lèi),CarWrapper是一個(gè)包裝類(lèi),當(dāng)獲取BenzCar的時(shí)候?qū)嶋H獲取的是被CarWrapper包裝后的對(duì)象,類(lèi)似代理模式
自動(dòng)加載
如果一個(gè)擴(kuò)展類(lèi)是另一個(gè)擴(kuò)展類(lèi)的成員變量,并且擁有set方法,框架會(huì)自動(dòng)注入這個(gè)擴(kuò)展點(diǎn)的實(shí)例,即IOC。先定義2個(gè)擴(kuò)展點(diǎn)
org.apache.dubbo.ioc.Car(resources\META-INF\services目錄下)
- benz=org.apache.dubbo.ioc.BenzCar
org.apache.dubbo.ioc.Wheel(resources\META-INF\services目錄下)
- benz=org.apache.dubbo.ioc.BenzWheel
- @SPI
- public interface Wheel {
- void getBrandByUrl();
- }
- public class BenzWheel implements Wheel {
- @Override
- public void getBrandByUrl() {
- System.out.println("benzWheel");
- }
- }
- @SPI
- public interface Car {
- void getBrandByUrl();
- }
- public class BenzCar implements Car {
- private Wheel wheel;
- public void setWheel(Wheel wheel) {
- this.wheel = wheel;
- }
- @Override
- public void getBrandByUrl() {
- System.out.println("benzCar");
- wheel.getBrandByUrl();
- }
- }
測(cè)試demo
- public class DubboSpiIocDemo {
- public static void main(String[] args) {
- ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
- Car car = extensionLoader.getExtension("benz");
- car.getBrandByUrl();
- }
- }
我跑這個(gè)代碼的時(shí)候直接報(bào)異常,看了一下官網(wǎng)才發(fā)現(xiàn)dubbo是可以注入接口的實(shí)現(xiàn)的,但不像spring那么智能,
dubbo必須用URL(類(lèi)似總線)來(lái)指定擴(kuò)展類(lèi)對(duì)應(yīng)的實(shí)現(xiàn)類(lèi).。這就不得不提到@Adaptive注解了
自適應(yīng)
使用@Adaptive注解,動(dòng)態(tài)的通過(guò)URL中的參數(shù)來(lái)確定要使用哪個(gè)具體的實(shí)現(xiàn)類(lèi)
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD})
- public @interface Adaptive {
- String[] value() default {};
- }
- @SPI
- public interface Wheel {
- @Adaptive("wheel")
- void getBrandByUrl(URL url);
- }
- public class BenzWheel implements Wheel {
- @Override
- public void getBrandByUrl(URL url) {
- System.out.println("benzWheel");
- }
- }
- @SPI
- public interface Car {
- void getBrandByUrl(URL url);
- }
- public class BenzCar implements Car {
- // 這個(gè)里面存的是代理對(duì)象
- private Wheel wheel;
- public void setWheel(Wheel wheel) {
- this.wheel = wheel;
- }
- @Override
- public void getBrandByUrl(URL url) {
- System.out.println("benzCar");
- // 代理類(lèi)根據(jù)URL找到實(shí)現(xiàn)類(lèi),然后再調(diào)用實(shí)現(xiàn)類(lèi)
- wheel.getBrandByUrl(url);
- }
- }
- public class DubboSpiIocDemo {
- public static void main(String[] args) {
- ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
- Car car = extensionLoader.getExtension("benz");
- Map<String, String> map = new HashMap<>();
- // 指定wheel的實(shí)現(xiàn)類(lèi)為benz
- map.put("wheel", "benz");
- URL url = new URL("", "", 1, map);
- // benzCar
- // benzWheel
- car.getBrandByUrl(url);
- }
- }
可以看到BenzCar對(duì)象成功注入了BenzWheel。BenzCar中其實(shí)注入的是BenzWheel的代碼對(duì)象,這個(gè)代理對(duì)象會(huì)根據(jù)@Adaptive("wheel")獲取到wheel,然后從url中找到key為wheel的值,這個(gè)值即為實(shí)現(xiàn)類(lèi)對(duì)應(yīng)的key。
上面的注釋提到BenzCar里面注入的Wheel其實(shí)是一個(gè)代理對(duì)象(框架幫我們生成),在代理對(duì)象中根據(jù)url找到相應(yīng)的實(shí)現(xiàn)類(lèi),然后調(diào)用實(shí)現(xiàn)類(lèi)。
因?yàn)榇韺?duì)象是框架在運(yùn)行過(guò)程中幫我們生成的,沒(méi)有文件可以查看,所以用Arthas來(lái)查看一下生成的代理類(lèi)
- curl -O https://alibaba.github.io/arthas/arthas-boot.jar
- java -jar arthas-boot.jar
- # 根據(jù)前面的序號(hào)選擇進(jìn)入的進(jìn)程,然后執(zhí)行下面的命令
- jad org.apache.dubbo.adaptive.Wheel$Adaptive
生成的Wheel
- package org.apache.dubbo.adaptive;
- import org.apache.dubbo.adaptive.Wheel;
- import org.apache.dubbo.common.URL;
- import org.apache.dubbo.common.extension.ExtensionLoader;
- public class Wheel$Adaptive
- implements Wheel {
- public void getBrandByUrl(URL uRL) {
- if (uRL == null) {
- throw new IllegalArgumentException("url == null");
- }
- URL uRL2 = uRL;
- String string = uRL2.getParameter("wheel");
- if (string == null) {
- throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.adaptive.Wheel) name from url (").append(uRL2.toString()).append(") use keys([wheel])").toString());
- }
- Wheel wheel = (Wheel)ExtensionLoader.getExtensionLoader(Wheel.class).getExtension(string);
- wheel.getBrandByUrl(uRL);
- }
- }
@Adaptive可以標(biāo)記在類(lèi)上或者方法上
標(biāo)記在類(lèi)上:將該實(shí)現(xiàn)類(lèi)直接作為默認(rèn)實(shí)現(xiàn),不再自動(dòng)生成代碼
標(biāo)記在方法上:通過(guò)參數(shù)動(dòng)態(tài)獲得實(shí)現(xiàn)類(lèi),比如上面的例子
用源碼演示一下用在類(lèi)上的@Adaptiv,Dubbo為自適應(yīng)擴(kuò)展點(diǎn)生成代碼,如我們上面的WheelAdaptive類(lèi)如下所示¨G30G∗∗@Adaptive可以標(biāo)記在類(lèi)上或者方法上∗∗標(biāo)記在類(lèi)上:將該實(shí)現(xiàn)類(lèi)直接作為默認(rèn)實(shí)現(xiàn),不再自動(dòng)生成代碼標(biāo)記在方法上:通過(guò)參數(shù)動(dòng)態(tài)獲得實(shí)現(xiàn)類(lèi),比如上面的例子用源碼演示一下用在類(lèi)上的@Adaptiv,Dubbo為自適應(yīng)擴(kuò)展點(diǎn)生成代碼,如我們上面的WheelAdaptive,但生成的代碼還需要編譯才能生成class文件。我們可以用JavassistCompiler(默認(rèn)的)或者JdkCompiler來(lái)編譯(需要配置),這個(gè)小小的功能就用到了@Adaptive
如果想用JdkCompiler需要做如下配置
- <dubbo:application compiler="jdk" />
Compiler類(lèi)圖如下
- @SPI("javassist")
- public interface Compiler {
- Class<?> compile(String code, ClassLoader classLoader);
- }
Compiler用@SPI指定了默認(rèn)實(shí)現(xiàn)類(lèi)為javassist
源碼中獲取Compiler調(diào)用了如下方法
- org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
getAdaptiveExtension()會(huì)獲取自適應(yīng)擴(kuò)展類(lèi),那么這個(gè)自適應(yīng)擴(kuò)展類(lèi)是誰(shuí)呢?
是AdaptiveCompiler,因?yàn)轭?lèi)上有@Adaptive注解
- @Adaptive
- public class AdaptiveCompiler implements Compiler {
- private static volatile String DEFAULT_COMPILER;
- public static void setDefaultCompiler(String compiler) {
- DEFAULT_COMPILER = compiler;
- }
- /**
- * 獲取對(duì)應(yīng)的Compiler,并調(diào)用compile做編譯
- * 用戶設(shè)置了compiler,就用設(shè)置了的,不然就用默認(rèn)的
- */
- @Override
- public Class<?> compile(String code, ClassLoader classLoader) {
- Compiler compiler;
- ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
- String name = DEFAULT_COMPILER; // copy reference
- if (name != null && name.length() > 0) {
- // 用用戶設(shè)置的
- compiler = loader.getExtension(name);
- } else {
- // 用默認(rèn)的
- compiler = loader.getDefaultExtension();
- }
- return compiler.compile(code, classLoader);
- }
- }
從compile方法可以看到,如果用戶設(shè)置了編譯方式,則用用戶設(shè)置的,如果沒(méi)有設(shè)置則用默認(rèn)的,即JavassistCompiler
自動(dòng)激活
使用@Activate注解,可以標(biāo)記對(duì)應(yīng)的擴(kuò)展點(diǎn)默認(rèn)被激活使用
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD})
- public @interface Activate {
- // 所屬組,例如消費(fèi)端,服務(wù)端
- String[] group() default {};
- // URL中包含屬性名為value的鍵值對(duì),過(guò)濾器才處于激活狀態(tài)
- String[] value() default {};
- // 指定執(zhí)行順序,before指定的過(guò)濾器在該過(guò)濾器之前執(zhí)行
- @Deprecated
- String[] before() default {};
- // 指定執(zhí)行順序,after指定的過(guò)濾器在該過(guò)濾器之后執(zhí)行
- @Deprecated
- String[] after() default {};
- // 指定執(zhí)行順序,值越小,越先執(zhí)行
- int order() default 0;
- }
可以通過(guò)指定group或者value,在不同條件下獲取自動(dòng)激活的擴(kuò)展點(diǎn)。before,after,order是用來(lái)排序的,感覺(jué)一個(gè)order參數(shù)就可以搞定排序的功能,所以官方把before,after標(biāo)記為@Deprecated
Dubbo Filter就是基于這個(gè)來(lái)實(shí)現(xiàn)的。Dubbo Filter是Dubbo可擴(kuò)展性的一個(gè)體現(xiàn),可以在調(diào)用過(guò)程中對(duì)請(qǐng)求進(jìn)行進(jìn)行增強(qiáng)
我寫(xiě)個(gè)demo演示一下這個(gè)自動(dòng)激活是怎么工作的
- @SPI
- public interface MyFilter {
- void filter();
- }
consumer組能激活這個(gè)filter
- @Activate(group = {"consumer"})
- public class MyConsumerFilter implements MyFilter {
- @Override
- public void filter() {
- }
- }
provider組能激活這個(gè)filter
- @Activate(group = {"provider"})
- public class MyProviderFilter implements MyFilter {
- @Override
- public void filter() {
- }
- }
consumer組和provide組都能激活這個(gè)filter
- @Activate(group = {"consumer", "provider"})
- public class MyLogFilter implements MyFilter {
- @Override
- public void filter() {
- }
- }
consumer組和provide組都能激活這個(gè)filter,同時(shí)url中指定key的value為cache
- @Activate(group = {"consumer", "provider"}, value = "cache")
- public class MyCacheFilter implements MyFilter {
- @Override
- public void filter() {
- }
- }
測(cè)試類(lèi)如下
getActivateExtension有3個(gè)參數(shù),依次為url, key, group
- public class ActivateDemo {
- public static void main(String[] args) {
- ExtensionLoader<MyFilter> extensionLoader = ExtensionLoader.getExtensionLoader(MyFilter.class);
- // url中沒(méi)有參數(shù)
- URL url = URL.valueOf("test://localhost");
- List<MyFilter> allFilterList = extensionLoader.getActivateExtension(url, "", null);
- /**
- * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
- * org.apache.dubbo.activate.MyProviderFilter@73a8dfcc
- * org.apache.dubbo.activate.MyLogFilter@ea30797
- *
- * 不指定組則所有的Filter都被激活
- */
- allFilterList.forEach(item -> System.out.println(item));
- System.out.println();
- List<MyFilter> consumerFilterList = extensionLoader.getActivateExtension(url, "", "consumer");
- /**
- * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
- * org.apache.dubbo.activate.MyLogFilter@ea30797
- *
- * 指定consumer組,則只有consumer組的Filter被激活
- */
- consumerFilterList.forEach(item -> System.out.println(item));
- System.out.println();
- // url中有參數(shù)myfilter
- url = URL.valueOf("test://localhost?myfilter=cache");
- List<MyFilter> customerFilter = extensionLoader.getActivateExtension(url, "myfilter", "consumer");
- /**
- * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
- * org.apache.dubbo.activate.MyLogFilter@ea30797
- * org.apache.dubbo.activate.MyCacheFilter@aec6354
- *
- * 指定key在consumer組的基礎(chǔ)上,MyCacheFilter被激活
- */
- customerFilter.forEach(item -> System.out.println(item));
- System.out.println();
- }
- }
總結(jié)一下就是,getActivateExtension不指定組就是激活所有的Filter,指定組則激活指定組的Filter。指定key則從Url中根據(jù)key取到對(duì)應(yīng)的value,假設(shè)為cache,然后把@Activate注解中value=cache的Filter激活
即group用來(lái)篩選,value用來(lái)追加,Dubbo Filter就是靠這個(gè)屬性激活不同的Filter的
ExtensionLoader的工作原理
ExtensionLoader是整個(gè)Dubbo SPI的主要實(shí)現(xiàn)類(lèi),有如下三個(gè)重要方法,搞懂這3個(gè)方法基本上就搞懂Dubbo SPI了。
加載擴(kuò)展類(lèi)的三種方法如下
- getExtension(),獲取普通擴(kuò)展類(lèi)
- getAdaptiveExtension(),獲取自適應(yīng)擴(kuò)展類(lèi)
- getActivateExtension(),獲取自動(dòng)激活的擴(kuò)展類(lèi)
getExtension()上面的例子中已經(jīng)有了。自適應(yīng)的特性上面已經(jīng)演示過(guò)了,當(dāng)獲取Wheel的實(shí)現(xiàn)類(lèi)是框架會(huì)調(diào)用getAdaptiveExtension()方法。
代碼就不放了,這3個(gè)方法的執(zhí)行過(guò)程還是比較簡(jiǎn)單的,如果你有看不懂的,可以看我給源碼加的注釋。
https://github.com/erlieStar/dubbo-analysis
理解了Dubbo SPI你應(yīng)該就把Dubbo搞懂一半了,剩下就是一些服務(wù)導(dǎo)出,服務(wù)引入,服務(wù)調(diào)用的過(guò)程了
本文轉(zhuǎn)載自微信公眾號(hào)「Java識(shí)堂」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java識(shí)堂公眾號(hào)。





























