0%

浅谈Dubbo SPI机制

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

abstract.png

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") //这里名为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

figure 1.png

现在,我们来测试下

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();
}
}

效果如下所示

figure 2.png

自适应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") //这里名为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);

/**
* 该方法未添加 @Adaptive 注解
*/
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

figure 3.png

测试:@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);
}
}

figure 4.png

测试:@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"));
}
}

figure 5.png

测试:@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"));
}
}

figure 6.png

测试:调用未添加@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());
}

}
}

figure 7.png

测试:组合拳

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);
}
}

figure 8.png

自定义的自适应实现类

@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) {
// 获取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

figure 9.png

现在我们来测试下

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);
}
}

figure 10.png

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;

/**
* 包装类需要包含一个仅SPI接口类型参数的构造器
*/
public LogWrapper(Cook cook) {
this.cook = cook;
}

/**
* 包装类在实现SPI接口时,添加增强逻辑,然后对请求进行转发即可
*/
@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;

/**
* 包装类需要包含一个仅SPI接口类型参数的构造器
*/
public PreWrapper(Cook cook) {
this.cook = cook;
}

/**
* 包装类在实现SPI接口时,添加增强逻辑,然后对请求进行转发即可
*/
@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

figure 11.png

测试代码如下

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();
}
}

结果如下所示

figure 12.png

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{
/**
* 这里SPI接口Plane的具体实现类AirbusPlane 依赖了 另外一个SPI接口GPS
*/
private GPS gps;

/**
* 注入方式为setter方式,故必须提供一个public的setter方法
* @param gps
*/
public void setGps(GPS gps) {
this.gps = gps;
}

@Override
public void takeOff(URL url) {
System.out.println("空客飞机: 准备起飞");
// 由于注入的是自适应实现类的实例,故其可以根据url来动态确定所使用的GPS实现类
gps.navigate(url);
}

@Override
public void landing(URL url) {
System.out.println("空客飞机: 正在降落");
// 由于注入的是自适应实现类的实例,故其可以根据url来动态确定所使用的GPS实现类
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{
/**
* 这里SPI接口Plane的具体实现类BoeingPlane 依赖了 另外一个SPI接口GPS
*/
private GPS gps;

/**
* 注入方式为setter方式,故必须提供一个public的setter方法
* 但在该setter方法上添加 @DisableInject注解 时,将不会对其进行注入
* @param 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

figure 13.png

测试用例如下所示

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"));

}
}

测试结果如下所示

figure 14.png

注入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;

/**
* 注入方式为setter方式,故必须提供一个public的setter方法
* 其中,setXxxYyy方法会注入名为xxxYyy的Spring Bean实例
* 故这里注入的就是名为chineseBook的Spring Bean
* @param 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;

/**
* 在setter方法上添加了 @DisableInject 注解时,将不会对其进行注入
* @param 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

figure 15.png

最后,我们必须要在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~~";
}
}

测试结果如下所示

figure 16.png

请我喝杯咖啡捏~
  • 本文作者: Aaron Zhu
  • 本文链接: https://xyzghio.xyz/DubboSPI/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-ND 许可协议。转载请注明出处!

欢迎关注我的微信公众号:青灯抽丝