Dubbo自己实现了一套SPI机制,没有使用Java自带的SPI,为了进行对比,我们仍然从Java SPI开始讲起。
1 Java SPI
新建接口和实现类:
package test.spi;
public interface Animal {
void sayHello();
}
package test.spi;
public class Dog implements Animal {
@Override
public void sayHello() {
System.out.println("Hello, I'm a dog.");
}
}
package test.spi;
public class Cat implements Animal {
@Override
public void sayHello() {
System.out.println("Hello, I'm a cat.");
}
}
在resources/META-INF/services下新建文件,文件名为接口全限定名:test.spi.Animal
文件内容:
test.spi.Dog
test.spi.Cat
测试下:
package test;
import org.apache.dubbo.common.extension.ExtensionLoader;
import test.spi.Animal;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
testJavaSPI();
}
private static void testJavaSPI() {
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
serviceLoader.forEach(Animal::sayHello);
}
}
ServiceLoader继承了Iterable接口,会去resources/META-INF/services下面去读取所有文件和文件中的接口实现。
输出:
Hello, I'm a dog.
Hello, I'm a cat.
2 Dubbo SPI
Dubbo没有使用Java SPI,而是自定义了一套SPI机制。
区别1:接口上增加"@SPI"注解;
package test.spi;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface Animal {
void sayHello();
}
区别2:扩展点配置文件位置放在/META-INF/dubbo/下面;不过dubbo兼容了java spi,因此也可以放在/META-INF/services/下面,或者/META-INF/dubbo/internal下面。
区别3:扩展点配置文件是键值对的形式
dog=test.spi.Dog
cat=test.spi.Cat
可以使用 @SPI("dog") 注解指定默认实现类。
测试:
package test;
import org.apache.dubbo.common.extension.ExtensionLoader;
import test.spi.Animal;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
testDubboSPI();
}
private static void testDubboSPI() {
ExtensionLoader<Animal> extensionLoader =
ExtensionLoader.getExtensionLoader(Animal.class);
Animal dog = extensionLoader.getExtension("dog");
dog.sayHello();
Animal cat = extensionLoader.getExtension("cat");
cat.sayHello();
}
}
可以看到,在dubbo spi中可以指定实现类,而java spi则只能遍历所有的实现类。
3 Dubbo SPI源码(版本2.7.3)
下面阅读下源码看下 getExtension方法是如何加载配置文件中的test.spi.Dog类的。分析中去掉了无关紧要的代码,只保留核心代码。
public T getExtension(String name) { // name = "dog"
// holder持有目标对象,其实只做了new Holder<>(),暂时可以不看
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) { // 同步局部变量,是为了防止外部修改holder在堆中的对象
instance = holder.get();
if (instance == null) {
// 创建Dog实例,看下一段代码
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
// getExtensionClasses会加载配置文件中配置的所有扩展类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) { // clazz: test.spi.Dog
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 重点来了:clazz.newInstance(),通过反射创建Dog类的实例instance,并放到缓存EXTENSION_INSTANCES中
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向instance注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
// 遍历包装类,获取其参数为type(就是Animal接口)的构造函数,注入type的实例instance
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
4 实战
扩展Protocol,新增jsonrpc协议:
接着在dubbo-provider.xml中新增协议就可以使用了:
<dubbo:protocol id="jsonrpc" name="jsonrpc" server="servlet" contextpath="dubbo" />
JsonRpcProtocol重写了doExport、doRefer,支持POST方式调用,在RPC调用时可以在header中携带Dubbo-Attachments隐式传参。
在服务暴露时,端口号和handler绑定:
public static void addHttpHandler(int port, HttpHandler processor) {
handlers.put(port, processor);
}
在web.xml中配置servlet:
<servlet>
<servlet-name>dubbo</servlet-name>
<servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dubbo</servlet-name>
<url-pattern>/dubbo/*</url-pattern>
</servlet-mapping>
这样当tomcat收到dubbo开头的url请求时,就会将请求转发到com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet进行处理:
String headers = request.getHeader(DUBBO_ATTACHMENT_HEADER);
if (headers != null) {
for (String header : headers.split(",")) {
int index = header.indexOf("=");
if (index > 0) {
String key = header.substring(0, index);
String value = header.substring(index + 1);
if (!StringUtils.isEmpty(key)) {
// 把header中的参数添加到RpcContext,RpcContext是一个ThreadLocal变量:ThreadLocal<RpcContext>
RpcContext.getContext().setAttachment(key.trim(), value.trim());
}
}
}
}