Dubbo原理(一)——SPI扩展点

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值