面試官從Dubbo泛化調用問到設計模式,咱們聊了三十分鐘

歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習java

1 泛化調用實例

對於JAVA服務端開發者而言在使用Dubbo時並不常常使用泛化調用,一般方法是在生產者發佈服務以後,消費者能夠經過引入生產者提供的client進行調用。那麼泛化調用使用場景是什麼呢?面試

第一種場景是消費者不但願引入生產者提供的client依賴,只但願關注調用哪一個方法,須要傳什麼參數便可。第二種場景是消費者不是使用Java語言,而是使用例如Python語言,那麼如何調用使用Java語言生產者提供的服務呢?這時咱們能夠選擇泛化調用。spring

泛化調用使用方法並不複雜,下面咱們編寫一個泛化調用實例。首先生產者發佈服務,這與普通服務發佈沒有任何區別。apache

package com.java.front.dubbo.demo.provider;

public interface HelloService {
    public String sayHelloGeneric(Person person, String message);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHelloGeneric(Person person, String message) throws Exception {
        String result = "hello[" + person + "],message=" + message;
        return result;
    }
}

Person類聲明:設計模式

package com.java.front.dubbo.demo.provider.model;

public class Person implements Serializable {
    private String name;
}

provider.xml文件內容:微信

<beans xmlns="http://www.springframework.org/schema/beans"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"     xsi:schemaLocation="http://www.springframework.org/schema/beans             http://www.springframework.org/schema/beans/spring-beans-4.3.xsd             http://code.alibabatech.com/schema/dubbo             http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

 <!-- 提供方應用信息,用於計算依賴關係 -->
 <dubbo:application name="java-front-provider" />

 <!-- 鏈接註冊中心 -->
 <dubbo:registry address="zookeeper://127.0.0.1:2181" />

 <!-- 生產者9999在端口暴露服務 -->
 <dubbo:protocol name="dubbo" port="9999" />
 
 <!-- Bean -->
 <bean id="helloService" class="com.java.front.dubbo.demo.provider.HelloServiceImpl" />
 
 <!-- 暴露服務 -->
 <dubbo:service interface="com.java.front.dubbo.demo.provider.HelloService" ref="helloService" />
</beans>

消費者代碼有所不一樣:架構

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.service.GenericService;

public class Consumer {
    public static void testGeneric() {
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
        reference.setApplication(new ApplicationConfig("java-front-consumer"));
        reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        reference.setInterface("com.java.front.dubbo.demo.provider.HelloService");
        reference.setGeneric(true);
        GenericService genericService = reference.get();
        Map<StringObject> person = new HashMap<StringObject>();
        person.put("name""微信公衆號「JAVA前線」");
        String message = "你好";
        Object result = genericService.$invoke("sayHelloGeneric"new String[] { "com.java.front.dubbo.demo.provider.model.Person""java.lang.String" }, new Object[] { person, message });
        System.out.println(result);
    }
}


 

2 Invoker

咱們經過源碼分析講解泛化調用原理,咱們首先須要瞭解Invoker這個Dubbo重量級概念。在生產者暴露服務流程整體分爲兩步,第一步是接口實現類轉換爲Invoker,第二步是Invoker轉換爲Exporter並放入ExporterMap,咱們看看生產者暴露服務流程圖:app

面試官從Dubbo泛化調用問到設計模式,咱們聊了三十分鐘

 

生產者經過ProxyFactory.getInvoker方法建立Invoker(AbstractProxyInvoker):jvm

public class JdkProxyFactory extends AbstractProxyFactory {

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {

                // proxy爲被代理對象 -> com.java.front.dubbo.demo.provider.HelloServiceImpl
                Method method = proxy.getClass().getMethod(methodName, parameterTypes);
                return method.invoke(proxy, arguments);
            }
        };
    }
}

 

咱們再看看消費者引用服務流程圖:async

面試官從Dubbo泛化調用問到設計模式,咱們聊了三十分鐘

 

消費者Invoker經過顯示實例化建立,例如本地暴露和遠程暴露都是經過顯示初始化的方法建立Invoker(AbstractInvoker):

new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap)
new DubboInvoker<T>(serviceType, url, getClients(url), invokers)

再經過ProxyFactory.getProxy建立代理:

public class JdkProxyFactory extends AbstractProxyFactory {
    @Override
    public <T> getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        InvokerInvocationHandler invokerInvocationHandler = new InvokerInvocationHandler(invoker);
        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, invokerInvocationHandler);
    }
}

不管是生產者仍是消費者的Invoker都實現自org.apache.dubbo.rpc.Invoker:

public abstract class AbstractInvoker<Timplements Invoker<T>{
}

public abstract class AbstractProxyInvoker<Timplements Invoker<T{
}


 

3 裝飾器模式

爲何生產者和消費者都要轉換爲Invoker而不是不直接調用呢?我認爲Invoker正是Dubbo設計精彩之處:真實調用都轉換爲Invoker,Dubbo就能夠經過裝飾器模式加強Invoker功能。咱們看看什麼是裝飾器模式。

裝飾器模式能夠動態將責任附加到對象上,在不改變原始類接口狀況下,對原始類功能進行加強,而且支持多個裝飾器的嵌套使用。實現裝飾器模式須要如下組件:

Component(抽象構件)
核心業務抽象:可使用接口或者抽象類
ConcreteComponent(具體構件)
實現核心業務:最終執行的業務代碼
Decorator(抽象裝飾器)
抽象裝飾器類:實現Component而且組合一個Component對象
ConcreteDecorator(具體裝飾器)
具體裝飾內容:裝飾核心業務代碼

咱們分析一個裝飾器實例。有一名足球運動員要去踢球,咱們用球鞋和球襪爲他裝飾一下,這樣可使戰力值增長。

(1) Component

/**  * 抽象構件(能夠用接口替代)  */
public abstract class Component {

    /**      * 踢足球(業務核心方法)      */
    public abstract void playFootBall();
}

(2) ConcreteComponent

/**  * 具體構件  */
public class ConcreteComponent extends Component {

    @Override
    public void playFootBall() {
        System.out.println("球員踢球");
    }
}

(3) Decorator

/**  * 抽象裝飾器  */
public abstract class Decorator extends Component {
    private Component component = null;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void playFootBall() {
        this.component.playFootBall();
    }
}

(4) ConcreteDecorator

/**  * 球襪裝飾器  */
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    /**      * 定義球襪裝飾邏輯      */
    private void decorateMethod() {
        System.out.println("換上球襪戰力值增長");
    }

    /**      * 重寫父類方法      */
    @Override
    public void playFootBall() {
        this.decorateMethod();
        super.playFootBall();
    }
}

/**  * 球鞋裝飾器  */
public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    /**      * 定義球鞋裝飾邏輯      */
    private void decorateMethod() {
        System.out.println("換上球鞋戰力值增長");
    }

    /**      * 重寫父類方法      */
    @Override
    public void playFootBall() {
        this.decorateMethod();
        super.playFootBall();
    }
}

(5) 測試代碼

public class TestDecoratorDemo {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component = new ConcreteDecoratorA(component);
        component = new ConcreteDecoratorB(component);
        component.playFootBall();
    }
}

// 換上球鞋戰力值增長
// 換上球襪戰力值增長
// 球員踢球


 

4 過濾器鏈路

Dubbo爲Invoker加強了哪些功能?過濾器鏈是我認爲加強的最重要的功能之一,咱們繼續分析源碼:

public class ProtocolFilterWrapper implements Protocol {

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 增長過濾器鏈
        Invoker<T> invokerChain = buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);
        return protocol.export(invokerChain);
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        // 增長過濾器鏈
        Invoker<T> invoker = protocol.refer(type, url);
        Invoker<T> result = buildInvokerChain(invoker, Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
        return result;
    }
}

不管是生產者仍是消費者都會建立過濾器鏈,咱們看看buildInvokerChain這個方法:

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;

        // (1)加載全部包含Activate註解的過濾器
        // (2)根據group過濾獲得過濾器列表
        // (3)Invoker最終被放到過濾器鏈尾部
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), keygroup);
        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;

                // 構造一個簡化Invoker
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        // 構造過濾器鏈路
                        Result result = filter.invoke(next, invocation);
                        if (result instanceof AsyncRpcResult) {
                            AsyncRpcResult asyncResult = (AsyncRpcResult) result;
                            asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
                            return asyncResult;
                        } else {
                            return filter.onResponse(result, invoker, invocation);
                        }
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }
}

加載全部包含Activate註解的過濾器,根據group過濾獲得過濾器列表,Invoker最終被放到過濾器鏈尾部,生產者最終生成鏈路:

EchoFilter -> ClassloaderFilter -> GenericFilter -> ContextFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter -> ExceptionFilter -> AbstractProxyInvoker

消費者最終生成鏈路:

ConsumerContextFilter -> FutureFilter -> MonitorFilter -> GenericImplFilter -> DubboInvoker


 

5 泛化調用原理

咱們終於即將看到泛化調用核心原理,咱們在生產者鏈路看到GenericFilter過濾器,消費者鏈路看到GenericImplFilter過濾器,正是這兩個過濾器實現了泛化調用。

(1) GenericImplFilter

@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        // 方法名=$invoke
        // invocation.getArguments()=("sayHelloGeneric", new String[] { "com.java.front.dubbo.demo.provider.model.Person", "java.lang.String" }, new Object[] { person, "你好" });
        if (invocation.getMethodName().equals(Constants.$INVOKE)
                && invocation.getArguments() != null
                && invocation.getArguments().length == 3
                && ProtocolUtils.isGeneric(generic)) {

            // 第一個參數表示方法名
            // 第二個參數表示參數類型
            // 第三個參數表示參數值 -> [{name=微信公衆號「JAVA前線」},你好]
            Object[] args = (Object[]) invocation.getArguments()[2];
            if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                for (Object arg : args) {
                    if (!(byte[].class == arg.getClass())) {
                        error(generic, byte[].class.getName(), arg.getClass().getName());
                    }
                }
            } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                for (Object arg : args) {
                    if (!(arg instanceof JavaBeanDescriptor)) {
                        error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                    }
                }
            }
            // 附加參數generic值設置爲true
            ((RpcInvocation) invocation).setAttachment(Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
        }
        // 繼續執行過濾器鏈路
        return invoker.invoke(invocation);
    }
}

(2) GenericFilter

@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {

        // RpcInvocation[methodName=$invoke, parameterTypes=[class java.lang.String, class [Ljava.lang.String;, class [Ljava.lang.Object;], arguments=[sayHelloGeneric, [Ljava.lang.String;@14e77f6b, [Ljava.lang.Object;@51e5f393], attachments={path=com.java.front.dubbo.demo.provider.HelloService, input=451, dubbo=2.0.2, interface=com.java.front.dubbo.demo.provider.HelloService, version=0.0.0, generic=true}]
        if (inv.getMethodName().equals(Constants.$INVOKE)
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {

            // sayHelloGeneric
            String name = ((String) inv.getArguments()[0]).trim();

            // [com.java.front.dubbo.demo.provider.model.Person, java.lang.String]
            String[] types = (String[]) inv.getArguments()[1];

            // [{name=微信公衆號「JAVA前線」}, 你好]
            Object[] args = (Object[]) inv.getArguments()[2];

            // RpcInvocation[methodName=sayHelloGeneric, parameterTypes=[class com.java.front.dubbo.demo.provider.model.Person, class java.lang.String], arguments=[Person(name=JAVA前線), abc], attachments={path=com.java.front.dubbo.demo.provider.HelloService, input=451, dubbo=2.0.2, interface=com.java.front.dubbo.demo.provider.HelloService, version=0.0.0, generic=true}]
            RpcInvocation rpcInvocation = new RpcInvocation(method, args, inv.getAttachments());
            Result result = invoker.invoke(rpcInvocation);
        }
    }
}


 

6 文章總結

本文首先介紹瞭如何使用泛化調用,並引出泛化調用爲何生效這個問題。第二點介紹了重量級概念Invoker,並引出爲何Dubbo要建立Invoker這個問題。第三點介紹了裝飾器模式如何加強功能。最後咱們經過源碼分析知道了過濾器鏈加強了Invoker功能而且是實現泛化調用的核心,但願本文對你們有所幫助。

歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習

相關文章
相關標籤/搜索