相比较于Java SPI机制,Dubbo实现了一套更为强大的SPI机制
POM依赖
引入dubbo依赖
1 2 3 4 5
| <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.2.4</version> </dependency>
|
基本实践:@SPI注解
首先定义一个Say接口。该接口需要添加 @SPI 注解。可选地,设置@SPI注解的value字段值。指定该接口的默认实现
1 2 3 4 5 6 7 8 9 10
| package com.aaron.application.dubbospi.basicdemo;
import org.apache.dubbo.common.extension.SPI;
@SPI("say1") public interface Say { void hello();
void bye(); }
|
然后对Say接口添加下述两个实现类:CatSay、DogSay
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.aaron.application.dubbospi.basicdemo;
public class CatSay implements Say{ @Override public void hello() { System.out.println("<Cat>: hi~~~"); }
@Override public void bye() { System.out.println("<Cat>: bye~~~"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.aaron.application.dubbospi.basicdemo;
public class DogSay implements Say{
@Override public void hello() { System.out.println("<Dog>: hi~~~"); }
@Override public void bye() { System.out.println("<Dog>: bye~~~"); } }
|
然后在 src/main/resources/META-INF/dubbo 路径下创建文件。其中,文件名为Say接口的全限定类名。这里即为com.aaron.application.dubbospi.basicdemo.Say。文件内容如下所示
1 2 3
| say1 = com.aaron.application.dubbospi.basicdemo.CatSay say2 = com.aaron.application.dubbospi.basicdemo.DogSay
|
现在,我们来测试下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.aaron.application.dubbospi.basicdemo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class BasicSpiDemo { public static void main(String[] args) { ExtensionLoader<Say> extensionLoader = ExtensionLoader. getExtensionLoader(Say.class);
System.out.println("------------------------ Test: say1 -------------------------"); Say say1 = extensionLoader.getExtension("say1"); say1.hello(); say1.bye();
System.out.println("------------------------ Test: say2 -------------------------"); Say say2 = extensionLoader.getExtension("say2"); say2.hello(); say2.bye();
System.out.println("------------------------ Test: say3 -------------------------"); Say say3 = extensionLoader.getDefaultExtension(); say3.hello(); say3.bye(); } }
|
效果如下所示
自适应SPI:@Adaptive注解
自适应SPI可通过 @Adaptive注解 添加在方法 或 类上实现。具体地:前者会由Dubbo框架自动生成相应的代理类(即,SPI接口的自适应实现类);后者则由开发者自定义SPI接口的自适应实现类
Dubbo框架自动生成的自适应实现类
在方法上添加@Adaptive注解,并在value字段中指定参数名。调用时,会根据url中相应参数的值来动态确定使用何种相应实现。具体地:
- @Adaptive注解的value字段不指定,则默认的参数名称规则为:对接口名称按驼峰进行拆分并转换为小写,并使用 . 点号连接
- @Adaptive注解的value字段中的参数名称允许有多个。调用时url中 如果第一个参数名不存在 或 其参数值为空,则会使用第二个参数名继续在url中查找,以此类推
- 当通过相应参数值依然不能确定实现后,会尝试使用默认实现
- 当 通过相应参数值确定的实现 或 默认实现 不存在时,调用结果会抛异常
- 添加@Adaptive注解的方法形参中必须要有URL类型的参数
- 该接口中如果存在未添加@Adaptive注解的方法,则调用该方法会抛异常
这里定义一个EatFood接口。其上需添加@SPI注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI;
@SPI("vegetable") public interface EatFood {
@Adaptive void eatBreakfast(URL url, String msg);
@Adaptive("david.like") void eatLunch(URL url);
@Adaptive({"aaron.like","tom.like"}) void eatDinner(URL url);
void eatAfternoonTea(); }
|
然后对EatFood接口添加下述实现类:EatFoodBeef、EatFoodFruit、EatFoodVegetable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL;
public class EatFoodBeef implements EatFood{ @Override public void eatBreakfast(URL url, String msg) { System.out.println("[早餐] : 吃牛肉" + msg); }
@Override public void eatLunch(URL url) { System.out.println("[中餐] : 吃牛肉"); }
@Override public void eatDinner(URL url) { System.out.println("[晚餐] : 吃牛肉"); }
@Override public void eatAfternoonTea() { System.out.println("[下午茶] : 吃牛肉"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL;
public class EatFoodFruit implements EatFood{ @Override public void eatBreakfast(URL url, String msg) { System.out.println("[早餐] : 吃水果" + msg); }
@Override public void eatLunch(URL url) { System.out.println("[中餐] : 吃水果"); }
@Override public void eatDinner(URL url) { System.out.println("[晚餐] : 吃水果"); }
@Override public void eatAfternoonTea() { System.out.println("[下午茶] : 吃水果"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL;
public class EatFoodVegetable implements EatFood{ @Override public void eatBreakfast(URL url, String msg) { System.out.println("[早餐] : 吃蔬菜" + msg); }
@Override public void eatLunch(URL url) { System.out.println("[中餐] : 吃蔬菜"); }
@Override public void eatDinner(URL url) { System.out.println("[晚餐] : 吃蔬菜"); }
@Override public void eatAfternoonTea() { System.out.println("[下午茶] : 吃蔬菜"); } }
|
同理,在 src/main/resources/META-INF/dubbo 路径下创建文件。其中,文件名为EatFood接口的全限定类名。这里即为com.aaron.application.dubbospi.adaptivedemo.EatFood。文件内容如下所示
1 2 3 4
| beef = com.aaron.application.dubbospi.adaptivedemo.EatFoodBeef fruit = com.aaron.application.dubbospi.adaptivedemo.EatFoodFruit vegetable = com.aaron.application.dubbospi.adaptivedemo.EatFoodVegetable
|
测试:@Adaptive注解未指定value字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class AdaptiveSpiDemo { public static void main(String[] args) { EatFood eatFood = ExtensionLoader.getExtensionLoader(EatFood.class).getAdaptiveExtension();
System.out.println("------------------ Test: @Adaptive注解未指定value字段 -----------------------"); String msg =",好好吃"; System.out.print("相应参数值确定的实现存在时"+"--->>>"); eatFood.eatBreakfast(URL.valueOf("localhost/demo2?eat.food=beef"), msg) ;
System.out.print("相应参数值确定的实现不存在时:抛异常"+"--->>>"); try{ eatFood.eatBreakfast(URL.valueOf("localhost/demo2?eat.food=apple"),msg) ; }catch (Exception e) { System.out.println("Happen exception: " + e.getMessage()); }
System.out.print("相应的参数值为空:使用默认实现"+"—-->>>"); eatFood.eatBreakfast(URL.valueOf("localhost/demo2?eat.food="), msg);
System.out.print("相应的参数不存在:使用默认实现" + "--->>>"); eatFood.eatBreakfast(URL.valueOf("localhost/demo2"), msg); } }
|
测试:@Adaptive注解Value字段指定一个参数名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class AdaptiveSpiDemo { public static void main(String[] args) { EatFood eatFood = ExtensionLoader.getExtensionLoader(EatFood.class).getAdaptiveExtension();
System.out.println("----------- Test: @Adaptive注解Value字段指定一个参数名 -----------------"); System.out.print("相应参数值确定的实现存在时"+"--->>>"); eatFood.eatLunch( URL.valueOf("localhost/demo2?david.like=fruit") );
System.out.print("相应参数值确定的实现不存在时:抛异常"+"--->>>"); try{ eatFood.eatLunch( URL.valueOf("localhost/demo2?david.like=pig") ); }catch (Exception e){ System.out.println("Happen exception: " + e.getMessage()); }
System.out.print("相应的参数值为空:使用默认实现"+"—-->>>"); eatFood.eatLunch( URL.valueOf("localhost/demo2?david.like="));
System.out.print("相应的参数不存在:使用默认实现" + "--->>>"); eatFood.eatLunch(URL.valueOf("localhost/demo2?num=2")); } }
|
测试:@Adaptive注解value字段指定多个参数名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class AdaptiveSpiDemo { public static void main(String[] args) { EatFood eatFood = ExtensionLoader.getExtensionLoader(EatFood.class).getAdaptiveExtension();
System.out.println("--------- Test:@Adaptive注解value字段指定多个参数名 -------------"); System.out.print("多个参数值同时存在时 —-->>>"); eatFood.eatDinner( URL.valueOf("localhost/demo2?tom.like=beef&aaron.like=fruit"));
System.out.print("第一个参数值为空,第二个参数值存在时"+"—-->>>"); eatFood.eatDinner( URL.valueOf("localhost/demo2?tom.like=beef&aaron.like=") );
System.out.print("第一个参数不存在,第二个参数值存在时"+"--->>>"); eatFood.eatDinner( URL.valueOf("localhost/demo2?tom.like=beef"));
System.out.print("第一个参数值确定的实现不存在时,第二个参数值存在时:抛异常"+"--->>>"); try{ eatFood.eatDinner( URL.valueOf("localhost/demo2?aaron.like=banana&tom.like=beef") ); }catch (Exception e) { System.out.println("Happen exception: " + e.getMessage()); }
System.out.print("参数值为空:使用默认实现" + "--->>>"); eatFood.eatDinner(URL.valueOf("localhost/demo2?aaron.like=&tom.like=") );
System.out.print("参数不存在 :使用默认实现" + "--->>>"); eatFood.eatDinner( URL.valueOf("localhost/demo2?num=2")); } }
|
测试:调用未添加@Adaptive注解的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class AdaptiveSpiDemo { public static void main(String[] args) { EatFood eatFood = ExtensionLoader.getExtensionLoader(EatFood.class).getAdaptiveExtension();
System.out. println("----------- Test: 调用未添加@Adaptive注解的方法 -------------"); try{ eatFood.eatAfternoonTea(); }catch (Exception e) { System.out.println("Happen exception: " + e.getMessage()); }
} }
|
测试:组合拳
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.aaron.application.dubbospi.adaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class AdaptiveSpiDemo { public static void main(String[] args) { EatFood eatFood = ExtensionLoader.getExtensionLoader(EatFood.class).getAdaptiveExtension();
System.out. println("----------- Test: 组合拳 -------------"); URL url = URL.valueOf("dubbo://localhost/demo1?eat.food=fruit&david.like=beef"); eatFood.eatBreakfast(url, msg); eatFood.eatLunch(url); eatFood.eatDinner(url); } }
|
自定义的自适应实现类
@Adaptive 注解也可以选择加在类上。此时dubbo将不会向此前那样(将注解加在SPI接口的方法上)来生成代理类了。换言之,此时自定义的自适应实现类需要实现SPI接口,通过硬编码来实现选择SPI接口具体实现的规则。这里我们先定义一个SPI接口
1 2 3 4 5 6 7 8 9 10 11
| package com.aaron.application.dubbospi.customadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.SPI;
@SPI public interface Drink { void drinkWater(URL url);
void drinkAlcohol(URL url); }
|
然后添加该SPI接口的两个实现类:ChineseDrink、AmericaDrink
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.aaron.application.dubbospi.customadaptivedemo;
import org.apache.dubbo.common.URL;
public class ChineseDrink implements Drink{ @Override public void drinkWater(URL url) { System.out.println("[喝水]: 喝农夫山泉"); }
@Override public void drinkAlcohol(URL url) { System.out.println("[喝酒]: 喝白酒"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.aaron.application.dubbospi.customadaptivedemo;
import org.apache.dubbo.common.URL;
public class AmericaDrink implements Drink{ @Override public void drinkWater(URL url) { System.out.println("[喝水]: 喝依云"); }
@Override public void drinkAlcohol(URL url) { System.out.println("[喝酒]: 喝威士忌"); } }
|
现在我们来实现自定义的自适应实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.aaron.application.dubbospi.customadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.ExtensionLoader;
@Adaptive public class MyAdaptiveDrink implements Drink{ @Override public void drinkWater(URL url) { Drink drink = getImplByUrl(url); drink.drinkWater(url); }
@Override public void drinkAlcohol(URL url) { Drink drink = getImplByUrl(url); drink.drinkAlcohol(url); }
private static Drink getImplByUrl(URL url) { String paramValue = url.getParameter("country"); ExtensionLoader<Drink> extensionLoader = ExtensionLoader.getExtensionLoader(Drink.class); Drink drink = extensionLoader.getExtension(paramValue); return drink; } }
|
同理,在 src/main/resources/META-INF/dubbo 路径下创建文件。其中,文件名为SPI接口Drink的全限定类名。这里即为com.aaron.application.dubbospi.customadaptivedemo.Drink。文件内容如下所示。其中,自定义的自适应实现类MyAdaptiveDrink也需要添加到该文件中, 名称随意
1 2 3 4 5 6
| CNDrink = com.aaron.application.dubbospi.customadaptivedemo.ChineseDrink USADrink = com.aaron.application.dubbospi.customadaptivedemo.AmericaDrink
CustomAdaptiveDrink = com.aaron.application.dubbospi.customadaptivedemo.MyAdaptiveDrink
|
现在我们来测试下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.aaron.application.dubbospi.customadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class CustomAdaptiveSpiDemo { public static void main(String[] args) { Drink drink = ExtensionLoader.getExtensionLoader(Drink.class).getAdaptiveExtension();
URL url1 = URL.valueOf("localhost/demo1?country=CNDrink"); drink.drinkWater(url1); drink.drinkAlcohol(url1);
System.out.println("--------------------------------------");
URL url2 = URL.valueOf("localhost/demo1?country=USADrink"); drink.drinkWater(url2); drink.drinkAlcohol(url2); } }
|
Dubbo SPI AOP
所谓Dubb的SPI AOP指的是通过Wrapper包装类,对该SPI接口的所有具体实现类进行增强。具体地:
- 包装类需要实现SPI接口
- 包装类需要包含SPI接口类型的属性,同时存在一个仅SPI接口类型参数的构造器
这里我们先定义一个SPI接口
1 2 3 4 5 6 7 8 9 10
| package com.aaron.application.dubbospi.spiaopdemo;
import org.apache.dubbo.common.extension.SPI;
@SPI public interface Cook { void cookLunch();
void cookDinner(); }
|
然后添加该SPI接口的两个实现类:CookChineseFood、CookWesternFood
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.aaron.application.dubbospi.spiaopdemo.impl;
import com.aaron.application.dubbospi.spiaopdemo.Cook;
public class CookChineseFood implements Cook { @Override public void cookLunch() { System.out.println("[烹饪中餐]: 猪肉炖粉条"); }
@Override public void cookDinner() { System.out.println("[烹饪晚餐]: 潮汕牛肉火锅"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.aaron.application.dubbospi.spiaopdemo.impl;
import com.aaron.application.dubbospi.spiaopdemo.Cook;
public class CookWesternFood implements Cook { @Override public void cookLunch() { System.out.println("[烹饪中餐]: 榴莲披萨"); }
@Override public void cookDinner() { System.out.println("[烹饪晚餐]: 牛排三明治"); } }
|
这里,我们添加下述两个包装类,来对上述的实现类进行增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.aaron.application.dubbospi.spiaopdemo.wrapper;
import com.aaron.application.dubbospi.spiaopdemo.Cook;
public class LogWrapper implements Cook { private Cook cook;
public LogWrapper(Cook cook) { this.cook = cook; }
@Override public void cookLunch() { System.out.println("开始做中餐..."); cook.cookLunch(); System.out.println("中餐制作完成"); }
@Override public void cookDinner() { System.out.println("开始做晚餐..."); cook.cookDinner(); System.out.println("晚餐制作完成"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.aaron.application.dubbospi.spiaopdemo.wrapper;
import com.aaron.application.dubbospi.spiaopdemo.Cook;
public class PreWrapper implements Cook { private Cook cook;
public PreWrapper(Cook cook) { this.cook = cook; }
@Override public void cookLunch() { System.out.println("厨师走进厨房"); cook.cookLunch(); System.out.println("厨师离开厨房"); }
@Override public void cookDinner() { System.out.println("厨师走进厨房"); cook.cookDinner(); System.out.println("厨师离开厨房"); } }
|
同理,在 src/main/resources/META-INF/dubbo 路径下创建文件。其中,文件名为SPI接口Cook的全限定类名。这里即为com.aaron.application.dubbospi.spiaopdemo.Cook。文件内容如下所示。其中,包装类也必须在配置文件中进行配置,但不需要配置名称
1 2 3 4 5 6 7
| chineseFood = com.aaron.application.dubbospi.spiaopdemo.impl.CookChineseFood westFood = com.aaron.application.dubbospi.spiaopdemo.impl.CookWesternFood
com.aaron.application.dubbospi.spiaopdemo.wrapper.LogWrapper com.aaron.application.dubbospi.spiaopdemo.wrapper.PreWrapper
|
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.aaron.application.dubbospi.spiaopdemo;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SpiAopDemoTest { public static void main(String[] args) { ExtensionLoader<Cook> extensionLoader = ExtensionLoader.getExtensionLoader(Cook.class); Cook cook1 = extensionLoader.getExtension("chineseFood"); System.out.println("-------------- Test : cook 1 lunch -------------------"); cook1.cookLunch();
System.out.println("-------------- Test : cook 1 dinner -------------------"); cook1.cookDinner();
Cook cook2 = extensionLoader.getExtension("westFood"); System.out.println("-------------- Test : cook 2 lunch -------------------"); cook2.cookLunch();
System.out.println("-------------- Test : cook 2 dinner -------------------"); cook2.cookDinner(); } }
|
结果如下所示
Dubbo SPI IOC
注入其他SPI实例
一个SPI接口的具体实现,如果依赖其他的SPI接口。则在加载该SPI实现时,会自动注入所依赖SPI接口的自适应实现类的实例。故所依赖的SPI接口必须具备自适应实现类。显然,@Adaptive注解加在类上自定义的自适应实现类 或 @Adaptive注解加在方法上由Dubbo框架自动生成的自适应实现类 均可。这里我们首先提供一个自适应SPI。SPI接口定义如下
1 2 3 4 5 6 7 8 9 10 11
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI;
@SPI public interface GPS { @Adaptive("gps.type") void navigate(URL url); }
|
然后添加该SPI接口GPS的两个实现类:BeiDouGPS、GalileoGPS
1 2 3 4 5 6 7 8 9 10
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL;
public class BeiDouGPS implements GPS{ @Override public void navigate(URL url) { System.out.println("北斗卫星导航系统启动..."); } }
|
1 2 3 4 5 6 7 8 9 10
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL;
public class GalileoGPS implements GPS{ @Override public void navigate(URL url) { System.out.println("伽利略卫星导航系统启动..."); } }
|
现在,我们来定义另外一个SPI接口Plane
1 2 3 4 5 6 7 8 9 10 11
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.SPI;
@SPI public interface Plane { void takeOff(URL url);
void landing(URL url); }
|
同理,为该SPI接口添加下述两个实现类:AirbusPlane、BoeingPlane。显然这两个实现类均依赖了另外一个SPI接口GPS。为此我们需要给gps属性添加一个public的setter方法,用于注入SPI接口GPS的自适应实现类。此外,我们还可以在setter方法上添加 @DisableInject注解,此时将不会对其进行注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL;
public class AirbusPlane implements Plane{
private GPS gps;
public void setGps(GPS gps) { this.gps = gps; }
@Override public void takeOff(URL url) { System.out.println("空客飞机: 准备起飞"); gps.navigate(url); }
@Override public void landing(URL url) { System.out.println("空客飞机: 正在降落"); gps.navigate(url); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.DisableInject;
public class BoeingPlane implements Plane{
private GPS gps;
@DisableInject public void setGps(GPS gps) { this.gps = gps; }
@Override public void takeOff(URL url) { System.out.println("波音飞机: 准备起飞"); if( gps==null ) { System.out.println("gps属性为null, 未被注入"); } else { gps.navigate(url); } }
@Override public void landing(URL url) { System.out.println("波音飞机: 正在降落"); if(gps==null) { System.out.println("gps属性为null, 未被注入"); } else { gps.navigate(url); } } }
|
同理,在 src/main/resources/META-INF/dubbo 路径下分别为SPI接口GPS、SPI接口Plane创建文件。其中,文件名分别为SPI接口GPS、SPI接口Plane的全限定类名。即com.aaron.application.dubbospi.spiiocadaptivedemo.GPS、com.aaron.application.dubbospi.spiiocadaptivedemo.Plane
com.aaron.application.dubbospi.spiiocadaptivedemo.GPS 文件内容如下
1 2 3
| beidou = com.aaron.application.dubbospi.spiiocadaptivedemo.BeiDouGPS galileo = com.aaron.application.dubbospi.spiiocadaptivedemo.GalileoGPS
|
com.aaron.application.dubbospi.spiiocadaptivedemo.Plane 文件内容如下
1 2 3
| boeing = com.aaron.application.dubbospi.spiiocadaptivedemo.BoeingPlane airbus = com.aaron.application.dubbospi.spiiocadaptivedemo.AirbusPlane
|
测试用例如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.aaron.application.dubbospi.spiiocadaptivedemo;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;
public class SpiIocAdaptiveDemoTest { public static void main(String[] args) { ExtensionLoader<Plane> extensionLoader = ExtensionLoader.getExtensionLoader(Plane.class);
Plane airbusPlane = extensionLoader.getExtension("airbus"); airbusPlane.takeOff(URL.valueOf("localhost/demo1?gps.type=galileo")); airbusPlane.landing(URL.valueOf("localhost/demo1?gps.type=beidou"));
System.out.println("-----------------------------------------------");
Plane boeingPlane = extensionLoader.getExtension("boeing"); boeingPlane.takeOff(URL.valueOf("localhost/demo1?gps.type=galileo")); boeingPlane.landing(URL.valueOf("localhost/demo1?gps.type=beidou"));
} }
|
测试结果如下所示
注入Spring Bean实例
一个SPI接口的具体实现,如果依赖Spring Bean实例。Dubbo SPI也可以识别、注入、使用该Spring Bean。这里我们先来定义一个普通的接口Book
1 2 3 4 5
| package com.aaron.application.dubbospi.spiiocbeandemo.book;
public interface Book { void readBook(); }
|
然后为该接口添加下述两个实现:ChineseBook、UsaBook。并为其添加@Component注解,使其成为Spring Bean
1 2 3 4 5 6 7 8 9 10 11
| package com.aaron.application.dubbospi.spiiocbeandemo.book;
import org.springframework.stereotype.Component;
@Component public class ChineseBook implements Book{ @Override public void readBook() { System.out.println("看《新华字典》"); } }
|
1 2 3 4 5 6 7 8 9 10 11
| package com.aaron.application.dubbospi.spiiocbeandemo.book;
import org.springframework.stereotype.Component;
@Component public class UsaBook implements Book{ @Override public void readBook() { System.out.println("看《牛津词典》"); } }
|
然后现在来定义一个SPI接口Study
1 2 3 4 5 6 7 8
| package com.aaron.application.dubbospi.spiiocbeandemo;
import org.apache.dubbo.common.extension.SPI;
@SPI public interface Study { void studyLanguage(); }
|
然后为其添加两个实现类:ChineseStudy、AmericaStudy。显然这两个实现类均依赖了Book类型的SpringBean。为此我们需要给book属性添加一个public的setter方法,用于注入指定名称的Spring Bean。其中,setXxxYyy方法会注入名为xxxYyy的Spring Bean实例。此外,我们还可以在setter方法上添加 @DisableInject注解,此时将不会对其进行注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.aaron.application.dubbospi.spiiocbeandemo;
import com.aaron.application.dubbospi.spiiocbeandemo.book.Book;
public class ChineseStudy implements Study{ private Book book;
public void setChineseBook(Book book) { this.book = book; }
@Override public void studyLanguage() { System.out.println("中国人正在学习母语..."); book.readBook(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.aaron.application.dubbospi.spiiocbeandemo;
import com.aaron.application.dubbospi.spiiocbeandemo.book.Book; import org.apache.dubbo.common.extension.DisableInject;
public class AmericaStudy implements Study{ private Book book;
@DisableInject public void setUsaBook(Book book) { this.book = book; }
@Override public void studyLanguage() { System.out.println("美国人正在学习母语..."); if( book==null ) { System.out.println("book属性为null,未注入"); } else { book.readBook(); } } }
|
同理,在 src/main/resources/META-INF/dubbo 路径下创建文件。其中,文件名为SPI接口Cook的全限定类名。这里即为com.aaron.application.dubbospi.spiiocbeandemo.Study。文件内容如下所示
1 2 3
| usaStudy = com.aaron.application.dubbospi.spiiocbeandemo.AmericaStudy cnStudy = com.aaron.application.dubbospi.spiiocbeandemo.ChineseStudy
|
最后,我们必须要在SprintBoot的启动类上添加@EnableDubbo注解。其会让Duboo框架在运行时加载Spring的应用上下文,以便Dubbo能够识别、使用Spring Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.aaron;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication @MapperScan("com.aaron.**") @EnableDubbo public class SpringBoot1Application { public static void main(String[] args) { SpringApplication.run(SpringBoot1Application.class, args); } }
|
现在,我们来写一个Controller类进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.aaron.application.dubbospi.spiiocbeandemo;
import org.apache.dubbo.common.extension.ExtensionLoader; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RequestMapping("/dubbospi") public class SpiIocBeanTestController {
@GetMapping("/test1") public String test1() { ExtensionLoader<Study> extensionLoader = ExtensionLoader.getExtensionLoader(Study.class);
Study usaStudy = extensionLoader.getExtension("usaStudy"); usaStudy.studyLanguage();
System.out.println("-------------------------------------------------");
Study cnStudy = extensionLoader.getExtension("cnStudy"); cnStudy.studyLanguage();
return "OK~~"; } }
|
测试结果如下所示