dubbo中的SPI机制

579 阅读4分钟

dubbo中通过对JDK的SPI增强实现来达到扩展点发现,刚看dubbo源码的时候对ExtensionLoader简直云里雾里,完全不知道loader.get会拿到啥,经过一系列的示例对dubbo的SPI有了初步的理解。(文中完整示例代码见:github.com/wp518cookie…
一、JDK中的SPI
1、JDK中通过ServiceLoader来加载需要自动发现接口扩展的实现类,要实现自动加载需要满足如下几个条件:
1)待扩展的接口类及实现类

// 待被扩展的类,有两个实现类 Male、Female
public interface Person {
    void saySomething();
}
public class Male implements Person {
    @Override
    public void saySomething() {
        System.out.println("i am male!");
    }
}
public class Female implements Person {
    @Override
    public void saySomething() {
        System.out.println("i am female!");
    }
}

2)在resources/META-INF/services目录下放置以需要被扩展接口的全限定名为文件名的文件com.ping.wu.extension.jdkspi.Person,文件中内容以行为单位,填上接口实现类的全限定名,如下:

com.ping.wu.extension.jdkspi.Female
com.ping.wu.extension.jdkspi.Male

3)获取扩展时代码如下:

public class JdkSpiTest {
  public static void main(String[] args) {
      ServiceLoader<Person> persons = ServiceLoader.load(Person.class);
      // 懒加载,通过hasNext触发加载
      Iterator<Person> it = persons.iterator();
      while (it.hasNext()) {
          Person person = it.next();
          person.saySomething();
      }
  }
}
输出内容为:
i am female!
i am male!

2、实现机制 通过iterator的hasNext()判断和执行next()时,会触发加载,加载过程就是找到之前约定好的文件名,获取到其中的实现类名,通过反射拿到class并实例化

c = Class.forName(implClassName, false, loader);
result = iterfaceClass.cast(c.newInstance());

3、为何dubbo不用? 很明显,这种加载方式不灵活,一个类的扩展必须加载所有他的实现类,每个扩展点只能通过遍历出来后的 getClass() 才能搞明白到底是哪个实现类,要获取指定的实现类需要遍历。dubbo每个扩展实现类都有key,有缓存key -> implClass的映射关系,同时扩展失败时会很明确地指明某key扩展失败。

二、dubbo中的SPI机制 配置方式大同小异,文件位置改为resource/META-INF/dubbo 文件名跟JDK的SPI相同,内容略有不同:

文件名:com.ping.wu.extension.dubbospi.CarMaker
f1carmaker=com.ping.wu.extension.dubbospi.F1CarMaker
racecarmaker=com.ping.wu.extension.dubbospi.RaceCarMaker
文件名:com.ping.wu.extension.dubbospi.WheelMaker
f1carwheelmaker=com.ping.wu.extension.dubbospi.F1CarWheelMaker
racecarwheelmaker=com.ping.wu.extension.dubbospi.RaceCarWheelMaker

1、@SPI注解可以理解为传统的JDK的SPI实现,只是该注解是有个value可以指定默认的扩展实现,先定义两个扩展点,一个是轮子Maker,一个是汽车Maker。轮子和汽车各有两个实现类:F1的轮子和汽车实现类,赛车的轮子和汽车实现类,具体接口定义示例如下(先忽略@Adaptive及@Activate注解)

轮子及实现类:
@SPI("racecarwheelmaker")
public interface WheelMaker {
    @Adaptive
    String make(URL url);
}
@Activate(group = "racecar")
public class RaceCarWheelMaker implements WheelMaker {
    @Override
    public String make(URL url) {
        return "race-car-wheel";
    }
}
@Activate(group = "f1")
public class F1CarWheelMaker implements WheelMaker {
    @Override
    public String make(URL url) {
        return "F1-car-wheel";
    }
}
车子及实现类
@SPI
public interface CarMaker {
    String make(URL url);
}
public class F1CarMaker implements CarMaker {
    private WheelMaker wheelMaker;
    @Override
    public String make(URL url) {
        return "F1 car with " + wheelMaker.make(url);
    }
    public WheelMaker getWheelMaker() {
        return wheelMaker;
    }
    public void setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
}
public class RaceCarMaker implements CarMaker {
    private WheelMaker wheelMaker;
    @Override
    public String make(URL url) {
        return null;
    }
    public WheelMaker getWheelMaker() {
        return wheelMaker;
    }
    public void setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
}

获取默认的扩展实现类:

SPI注解value的值为key去获取
WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class)
.getDefaultExtension();
指定key获取:
WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class)
.getExtension("f1carwheelmaker");

2、@Adaptive自适应注解 有这个注解的方法会生成一个代理适配器的类

public class WheelMaker$Adaptive implements com.ping.wu.extension.dubbospi.WheelMaker {
    public java.lang.String make(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) {
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("wheel.maker", "racecarwheelmaker");
        if (extName == null) {
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.spi.WheelMaker) name from url(" + url.toString() + ") use keys([wheel.maker])");
        }
        com.ping.wu.extension.dubbospi.WheelMaker extension = (com.ping.wu.extension.dubbospi.WheelMaker) ExtensionLoader.getExtensionLoader(com.ping.wu.extension.dubbospi.WheelMaker.class).getExtension(extName);
        return extension.make(arg0);
    }
}

里头有填@Adaptive("****"), 会以*****作为key,否则会把WheelMaker接口名根据大写字母以“.”分割,会根据URL中的key去获取对应的实现类,根据URL中key的不同实现动态的扩展
WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getAdaptiveExtension();
        System.out.println(wheelMaker.make(getMockURL("wheel.maker", "racecarwheelmaker")));

3、@Adaptive setter自动注入

如果一个属性也是@SPI注解的,比如car中的WheelMaker属性,则car会根据setter注入的wheel,跟对应的car类型配对起来
private static void testAdaptiveSetter() {
        CarMaker carMaker = ExtensionLoader.getExtensionLoader(CarMaker.class).getExtension("f1carmaker");
        System.out.println(carMaker.make(getMockURL("wheel.maker", "f1carwheelmaker")));
        System.out.println(carMaker.make(getMockURL("wheel.maker", "racecarwheelmaker")));
    }

4、@Activate 这个注解加在实现类上后,有一系列如group的属性,当指明gruop为**后,可以通过如下方式获取所有满足条件的扩展类

获取group为racecar的扩展类
private static void testActivate() {
        URL url = getMockURL(null, null);
        List<WheelMaker> wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getActivateExtension(url, new String(), "racecar");
        for (WheelMaker t : wheelMaker) {
            System.out.println(t.make(url));
        }
    }