Dubbo点滴(1) SPI入门

1.比较重要的注解

@SPI:扩展点接口的标识 :作用域在类上;

@Adaptive:为生成Adaptive实例提供参数,作用域在类或方法上;

    Adatpive,字面意思是个适配,但其实是个代理,它的意思是适配合适的对象处理请求。类似jdk的动态代理,因为dubbo底层会大量使用反射,出于性能考虑会默认使用javassist字节码编译生成一个adaptive拦截所有请求,然后由它基于策略动态委派合适的provider进行处理。 
SPI接口会动态编译出一个adaptive,用于适配provider处理请求。用户可以自己实现一个adaptive,只需要对某个provider打上@adaptive即可,例如Dubbo自身的AdaptiveExtensionFactory类。 
对于默认编译生成Adaptive的方案,需要使用@Adaptive声明接口上的哪些方法是adaptive方法,没有被声明的方法如果被请求会抛出异常非adaptive方法的异常

@Activate:可以被框架中自动**加载扩展,此Annotation用于配置扩展被自动**加载条件。

Activate,看起来有点不好理解,它的意思是条件**,用户通过group和value配置**条件。被activate注解的扩展点在满足某种条件时会被**,它一般用来配合filter和Invokelistener,声明他们的使用场景。

1.1 测试对象代码

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#1.声明SPI 默认为imp1
@SPI("impl1")
public interface SimpleExt {
    // 没有使用key的@Adaptive !
    @Adaptive
    String echo(URL url, String s);
     
    @Adaptive({"key1""key2"})
    String yell(URL url, String s);
 
    // 无@Adaptive !
    String bang(URL url, int i);
}
 
//实现类1
public class SimpleExtImpl1 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl1-echo";
    }
     
    public String yell(URL url, String s) {
        return "Ext1Impl1-yell";
    }
     
    public String bang(URL url, int i) {
        return "bang1";
    }
}
//实现类2
public class SimpleExtImpl2 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl2-echo";
    }
     
    public String yell(URL url, String s) {
        return "Ext1Impl2-yell";
    }
 
    public String bang(URL url, int i) {
        return "bang2";
    }
     
}
//实现类3
public class SimpleExtImpl3 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl3-echo";
    }
     
    public String yell(URL url, String s) {
        return "Ext1Impl3-yell";
    }
 
    public String bang(URL url, int i) {
        return "bang3";
    }
     
}

1.2 配置文件com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt

位置要放在如下位置

1
2
3
4
5
private static final String SERVICES_DIRECTORY = "META-INF/services/";
 
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

具体内容如下

1
2
3
4
# Comment 1
impl1=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl1#Hello World
impl2=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl2  # Comment 2
   impl3=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl3 # with head space

定义3个实现。

1.4 测试

1
2
3
4
5
6
7
8
@Test
public void test_getDefaultExtension() throws Exception {
    SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultExtension();
    assertThat(ext, instanceOf(SimpleExtImpl1.class));
     
    String name = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultExtensionName();
    assertEquals("impl1", name);
}

由于@SPI("impl1"),定义了默认实现的名称为imp1.

1
2
3
4
5
@Test
public void test_getExtension() throws Exception {
    assertTrue(ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension("impl1"instanceof SimpleExtImpl1);
    assertTrue(ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension("impl2"instanceof SimpleExtImpl2);
}

getExtensionLoader(Class<T> type):根据类名,返回具体实现类。这些配置信息在META对应文件中配置。当然,也可以使用@Extention注解配置(只不过,这个注解已经废弃了)

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
@Test
public void test_getAdaptiveExtension_defaultAdaptiveKey() throws Exception {
    {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
 
        Map<String, String> map = new HashMap<String, String>();
        //没有指定具体parameters参数,所以选用默认实现,最后返回impl1
        URL url = new URL("p1""1.2.3.4"1010"path1", map);
        //如果不设置默认的SPI实现类,则报异常
        //java.lang.IllegalStateException: Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(p1://1.2.3.4:1010/path1) use keys([simple.ext])
        String echo = ext.echo(url, "haha");
        assertEquals("Ext1Impl1-echo", echo);
    }
 
    {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
 
        Map<String, String> map = new HashMap<String, String>();
        map.put("simple.ext""impl2");//手动在参数中配置impl2,参数为simple.ext
        URL url = new URL("p1""1.2.3.4"1010"path1", map);
 
        String echo = ext.echo(url, "haha");
        assertEquals("Ext1Impl2-echo", echo);
    }
}

@Adaptive 测试

由于 yell方法声明了,@Adaptive({"key1", "key2"})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test_getAdaptiveExtension_customizeAdaptiveKey() throws Exception {
    SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
 
    Map<String, String> map = new HashMap<String, String>();
    map.put("key2""impl2");
    URL url = new URL("p1""1.2.3.4"1010"path1", map);
 
    String echo = ext.yell(url, "haha");
    assertEquals("Ext1Impl2-yell", echo);
 
    url = url.addParameter("key1""impl3"); // 注意: URL是值类型
    echo = ext.yell(url, "haha");
    assertEquals("Ext1Impl3-yell", echo);
}

如果参数不是key1,key2,即使参数值输入impl1,impl2也是无意义的。


由于bang方法,没有被@Adaptive 修饰,所以以下代码,会报异常

ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension().bang(..);

of interface com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt is not adaptive method!


[email protected]注解

Dubbo点滴(1) SPI入门

配置文件

1
2
3
4
5
group=com.alibaba.dubbo.common.extensionloader.activate.impl.GroupActivateExtImpl
value=com.alibaba.dubbo.common.extensionloader.activate.impl.ValueActivateExtImpl
order1=com.alibaba.dubbo.common.extensionloader.activate.impl.OrderActivateExtImpl1
order2=com.alibaba.dubbo.common.extensionloader.activate.impl.OrderActivateExtImpl2
com.alibaba.dubbo.common.extensionloader.activate.impl.ActivateExt1Impl1
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
@SPI("impl1")
public interface ActivateExt1 {
    String echo(String msg);
}
@Activate(group = {"default_group"})
public class ActivateExt1Impl1 implements ActivateExt1 {
    public String echo(String msg) {
        return msg;
    }
}
@Activate(group = {"group1""group2"})
public class GroupActivateExtImpl implements ActivateExt1 {
 
    public String echo(String msg) {
        return msg;
    }
}
@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt1 {
 
    public String echo(String msg) {
        return msg;
    }
}
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt1 {
 
    public String echo(String msg) {
        return msg;
    }
}
@Activate(value = {"value"}, group = {"value"})
public class ValueActivateExtImpl implements ActivateExt1 {
 
    public String echo(String msg) {
        return msg;
    }
}

2.2测试代码

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
   @Test
    public void testLoadActivateExtension() throws Exception {
        // test default
        URL url = URL.valueOf("test://localhost/test");
        List<ActivateExt1> list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                .getActivateExtension(url, new String[]{}, "default_group");
        Assert.assertEquals(1, list.size());
        Assert.assertTrue(list.get(0).getClass() == ActivateExt1Impl1.class);
 
        // test group
//        url = url.addParameter(Constants.GROUP_KEY, "group1");
        list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                .getActivateExtension(url, new String[]{}, "group1");
        Assert.assertEquals(1, list.size());
        Assert.assertTrue(list.get(0).getClass() == GroupActivateExtImpl.class);
 
        // test value
        url = url.removeParameter(Constants.GROUP_KEY);
//        url = url.addParameter(Constants.GROUP_KEY, "value");
        url = url.addParameter("value""value");
        list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                .getActivateExtension(url, new String[]{}, "value");
        Assert.assertEquals(1, list.size());
        Assert.assertTrue(list.get(0).getClass() == ValueActivateExtImpl.class);
 
        // test order
        url = URL.valueOf("test://localhost/test");
//        url = url.addParameter(Constants.GROUP_KEY, "order");
        list = ExtensionLoader.getExtensionLoader(ActivateExt1.class)
                .getActivateExtension(url, new String[]{}, "order");
        Assert.assertEquals(2, list.size());
        Assert.assertTrue(list.get(0).getClass() == OrderActivateExtImpl1.class);
        Assert.assertTrue(list.get(1).getClass() == OrderActivateExtImpl2.class);
    }

以上内容,是通过代码演练的方式,讲解了dubbo SPI机制的威力。
如果对底层实现感兴趣,可参看博客。




本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1880962,如需转载请自行联系原作者