扩展点加载机制主要是为了实现接口和实现的解耦,接口的具体实现并不在代码中指定,而是通过通过外部配置实现,Dubbo的扩展点加载是对JDK的SPI扩展点加强而来,大家如果不了解JDK的SPI也没有关系,这里我们主要来看一下Dubbo的实现
在Dubbo的实现中,主要有三个注解,@SPI、@Adaptive和@Activate,下面我们来结合例子分别看一下它们的使用
首先我们来定义一个接口和几个对应的实现
| 12
 3
 4
 5
 6
 
 | 
 @SPI("simple")
 public interface DemoService {
 String echo(URL url, String s);
 }
 
 | 
| 12
 3
 4
 5
 6
 
 | public class SimpleServiceImpl implements DemoService {@Override
 public String echo(URL url, String s) {
 return "SimpleService";
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 
 | public class ComplexServiceImpl implements DemoService {@Override
 public String echo(URL url, String s) {
 return "ComplexService";
 }
 }
 
 | 
使用时,我们需要在类路径下的META-INF/dubbo/或META-INF/dubbo/internal/路径下(也可以在java spi的/META-INF/services/下)创建与接口全路径相同的文件名,在其中以key-value形式指定各个实现类的全路径及对应名称(名称可以自己随意起),对于上面的例子我们可以这样写
| 12
 3
 
 | ## 文件名:com.test.spi.DemoServicesimple=com.test.spi.SimpleServiceImpl
 complex=com.test.spi.ComplexServiceImpl
 
 | 
由于上面我们在接口的SPI注解上指定了simple的实现,即SimpleServiceImpl实现类,写了测试类来验证一下
| 12
 3
 4
 5
 6
 7
 8
 
 | @Testpublic void testDefaultExtension() {
 final DemoService defaultExtension = ExtensionLoader.getExtensionLoader(DemoService.class)
 .getDefaultExtension();
 assertTrue(defaultExtension instanceof SimpleServiceImpl);
 final String echo = defaultExtension.echo(null, null);
 assertSame("SimpleService", echo);
 }
 
 | 
测试通过~, 简单的使用就是这样,如果要切换不同的实现,修改@SPI注解中的值为对应实现即可
但这时有一个问题,就是这样我们还是在代码里写死了具体的实现,如果我们想在运行时再决定使用哪一个实现该怎么半?这时就要靠@Adaptive注解了,我们来看一下
其实我们要动态选择的其实不是实现类,而是实现类的方法,所以现在我们将接口修改如下
| 12
 3
 4
 5
 6
 
 | @SPI("simple")public interface DemoService {
 
 @Adaptive("service")
 String echo(URL url, String s);
 }
 
 | 
到这里,不得不提一下dubbo中的URL类,它是dubbo中的配置关键类,几乎所有的配置想都是在其中以参数键值对的形式存在,SPI的配置当然也不例外
我们在@Adaptive的注解中指定了一个值(可以任意指定,也可不指定,不过不指定则是对应类名的转换,如DemoService则转为 demo.service)这个值有什么用呢,它会从URL中找到和它一样的key对应的value值,这个value值就是要使用的实现类的key(配置文件中设置的key)
上面我们指定了@Adaptive值为"service", 如果url中有"?service=complex",那么执行时则是使用complex=com.test.spi.ComplexServiceImpl对应的类来执行
如果没有找到怎么办呢?那就使用SPI注解中的实现–"simple"做为默认实现
Dubbo处理有@Adaptive注解的类时,会默认使用javassist生成一个类,对应上面的类,生成的类内容大致简化如下DemoService$Adaptive
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public class DemoService$Adaptive implements com.test.spi.DemoService {public String echo(URL arg0, String arg1) {
 URL url = arg0;
 
 String extName = url.getParameter("service", "simple");
 DemoService extension = (DemoService)ExtensionLoader.getExtensionLoader(DemoService.class).getExtension(extName);
 return extension.echo(arg0, arg1);
 }
 }
 
 | 
看了这段代码,相信大家也明白了这个动态功能的实现,下面我们写一个测试类来验证一下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | @Testpublic void testAdaptive() {
 final DemoService adaptiveExtension = ExtensionLoader.getExtensionLoader(DemoService.class)
 .getAdaptiveExtension();
 
 
 final URL url = new URL("", "", 100, "", "service", "complex");
 final String echo = adaptiveExtension.echo(url, null);
 assertSame("ComplexService", echo);
 }
 
 | 
到这里其实已经差不多够用了,但是还有一种情况,就是即使我们使用了@Adaptive注解,但是它只能选择一个类来执行,如果我们想匹配多个,并让它们都能得到执行怎么办呢?(如Dubbo中的过滤器)这时就需要@Activate出马了, 我们来修改一下代码
先新创建两个实现类,并添加@Activate注解
| 12
 3
 4
 5
 6
 7
 
 | @Activate(group = "act", value = "actone", order = 1)public class ActivateOneServiceImpl implements DemoService {
 @Override
 public String echo(URL url, String s) {
 return "ActivateOneService";
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 
 | @Activate(group = "act", value = "actTwo", order = 2)public class ActivateTwoServiceImpl implements DemoService {
 @Override
 public String echo(URL url, String s) {
 return "ActivateTwoService";
 }
 }
 
 | 
将它们添加到配置文件中去,这时的配置文件如下
| 12
 3
 4
 
 | simple=com.test.spi.SimpleServiceImplcomplex=com.test.spi.ComplexServiceImpl
 actone=com.test.spi.ActivateOneServiceImpl
 acttwo=com.test.spi.ActivateTwoServiceImpl
 
 | 
同样的,我们写个测试类来测试一下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | @Testpublic void testActivateTwo() {
 
 final URL url = new URL("", "", 100, "", "select", "actone,acttwo");
 final List<DemoService> activateExtension = ExtensionLoader.getExtensionLoader(DemoService.class)
 
 .getActivateExtension(url, "select", "act");
 assertEquals(2, activateExtension.size());
 final DemoService demoService1 = activateExtension.get(0);
 assertTrue(demoService1 instanceof ActivateOneServiceImpl);
 final DemoService demoService2 = activateExtension.get(1);
 assertTrue(demoService2 instanceof ActivateTwoServiceImpl);
 }
 
 | 
好了,基本的内容就介绍到这里,这里只是简单介绍了一下使用,还有许多内容没有说到,比如
- 
自动注入: 只支持setter方式注入 
- 
自动包装:在定义接口实现时,构造器仍然为一个此接口的参数,这时dubbo会认为它是一个wrapper(可以理解设计模式中的装饰器模式),对于此接口所有类的实现,都会通过这个包装类包装后返回 
- 
@Adaptive中的注解值可以指定多个,执行时会依次进行匹配 
- 
@Activate的匹配方式还有许多,不止是上面提到的那种,比如URL值的参数键和@Activate中的value匹配上的话也会激活 
等等等等,大家有兴趣的话可以继续研究下去