相比较于Java SPI机制,Dubbo实现了一套更为强大的SPI机制

POM依赖
引入dubbo依赖
| 12
 3
 4
 5
 
 | <dependency><groupId>org.apache.dubbo</groupId>
 <artifactId>dubbo</artifactId>
 <version>3.2.4</version>
 </dependency>
 
 | 
基本实践:@SPI注解
首先定义一个Say接口。该接口需要添加 @SPI 注解。可选地,设置@SPI注解的value字段值。指定该接口的默认实现
| 12
 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
| 12
 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~~~");
 }
 }
 
 | 
| 12
 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。文件内容如下所示
| 12
 3
 
 | say1 = com.aaron.application.dubbospi.basicdemo.CatSay
 say2 = com.aaron.application.dubbospi.basicdemo.DogSay
 
 | 

现在,我们来测试下
| 12
 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注解
| 12
 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
| 12
 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("[下午茶] : 吃牛肉");
 }
 }
 
 | 
| 12
 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("[下午茶] : 吃水果");
 }
 }
 
 | 
| 12
 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。文件内容如下所示
| 12
 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字段
| 12
 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字段指定一个参数名
| 12
 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字段指定多个参数名
| 12
 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注解的方法
| 12
 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());
 }
 
 }
 }
 
 | 

测试:组合拳
| 12
 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接口
| 12
 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
| 12
 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("[喝酒]: 喝白酒");
 }
 }
 
 | 
| 12
 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("[喝酒]: 喝威士忌");
 }
 }
 
 | 
现在我们来实现自定义的自适应实现类
| 12
 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也需要添加到该文件中, 名称随意
| 12
 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
 
 | 

现在我们来测试下
| 12
 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接口
| 12
 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
| 12
 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("[烹饪晚餐]: 潮汕牛肉火锅");
 }
 }
 
 | 
| 12
 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("[烹饪晚餐]: 牛排三明治");
 }
 }
 
 | 
这里,我们添加下述两个包装类,来对上述的实现类进行增强
| 12
 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("晚餐制作完成");
 }
 }
 
 | 
| 12
 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。文件内容如下所示。其中,包装类也必须在配置文件中进行配置,但不需要配置名称
| 12
 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
 
 | 

测试代码如下
| 12
 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接口定义如下
| 12
 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
| 12
 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("北斗卫星导航系统启动...");
 }
 }
 
 | 
| 12
 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
| 12
 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注解,此时将不会对其进行注入
| 12
 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);
 }
 }
 
 | 
| 12
 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 文件内容如下
| 12
 3
 
 | beidou = com.aaron.application.dubbospi.spiiocadaptivedemo.BeiDouGPS
 galileo = com.aaron.application.dubbospi.spiiocadaptivedemo.GalileoGPS
 
 | 
com.aaron.application.dubbospi.spiiocadaptivedemo.Plane 文件内容如下
| 12
 3
 
 | boeing = com.aaron.application.dubbospi.spiiocadaptivedemo.BoeingPlane
 airbus = com.aaron.application.dubbospi.spiiocadaptivedemo.AirbusPlane
 
 | 

测试用例如下所示
| 12
 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
| 12
 3
 4
 5
 
 | package com.aaron.application.dubbospi.spiiocbeandemo.book;
 public interface Book {
 void readBook();
 }
 
 | 
然后为该接口添加下述两个实现:ChineseBook、UsaBook。并为其添加@Component注解,使其成为Spring Bean
| 12
 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("看《新华字典》");
 }
 }
 
 | 
| 12
 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
| 12
 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注解,此时将不会对其进行注入
| 12
 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();
 }
 }
 
 | 
| 12
 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。文件内容如下所示
| 12
 3
 
 | usaStudy = com.aaron.application.dubbospi.spiiocbeandemo.AmericaStudy
 cnStudy = com.aaron.application.dubbospi.spiiocbeandemo.ChineseStudy
 
 | 

最后,我们必须要在SprintBoot的启动类上添加@EnableDubbo注解。其会让Duboo框架在运行时加载Spring的应用上下文,以便Dubbo能够识别、使用Spring Bean
| 12
 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类进行测试
| 12
 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~~";
 }
 }
 
 | 
测试结果如下所示
