dubbo泛化調(diào)用使用及原理解析

本文案例代碼見 git@github.com:shengchaojie/dubbo_best_practise.git

什么是泛化調(diào)用

通常我們想調(diào)用別人的dubbo服務(wù)時(shí),我們需要在項(xiàng)目中引入對(duì)應(yīng)的jar包。而泛化調(diào)用的作用是,我們無需依賴相關(guān)jar包,也能調(diào)用到該服務(wù)。

這個(gè)特性一般使用在網(wǎng)關(guān)類項(xiàng)目中,在業(yè)務(wù)開發(fā)中基本不會(huì)使用。

使用方式

假設(shè)我現(xiàn)在要調(diào)用下面的接口服務(wù)

package com.scj.demo.dubbo.provider.service.impl;

public interface ByeService {

    String bye(String name);

}

api

ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(new ApplicationConfig("test"));
referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
referenceConfig.setInterface("com.scj.demo.dubbo.provider.service.impl.ByeService");
referenceConfig.setGeneric(true);
GenericService genericService = referenceConfig.get();

Object result = genericService.$invoke(
    "bye",
    new String[]{"java.lang.String"},
    new Object[]{"1234"});

System.out.println(result);

spring

在xml文件做以下配置

<dubbo:reference id="byeService" interface="com.scj.demo.dubbo.provider.service.impl.ByeService" generic="true" />

然后注入使用

@Service
public class PersonService {

    @Resource(name = "byeService")
    private GenericService genericService;

    public void sayBye(){
        Object result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String"},
                new Object[]{"1234"});
        System.out.println(result);
    }

}

在兩種調(diào)用方式中,我們都需要使用被調(diào)用接口的字符串參數(shù)生成GenericService,通過GenericService的$invoke間接調(diào)用目標(biāo)接口的接口。

public interface GenericService {

    /**
     * Generic invocation
     *
     * @param method         Method name, e.g. findPerson. If there are overridden methods, parameter info is
     *                       required, e.g. findPerson(java.lang.String)
     * @param parameterTypes Parameter types
     * @param args           Arguments
     * @return invocation return value
     * @throws Throwable potential exception thrown from the invocation
     */
    Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;

}

$invoke的三個(gè)參數(shù)分別為,方法名,方法參數(shù)類型數(shù)組,方法參數(shù)數(shù)組。

方法入?yún)?gòu)造

可以看到泛化調(diào)用的一個(gè)復(fù)雜性在于$invoke的第三個(gè)參數(shù)的組裝,下面介紹幾種復(fù)雜入?yún)⒌恼{(diào)用方式

首先豐富提供者接口

public interface ByeService {

    String bye(String name);

    String bye(String name, Long age, Date date);

    String bye(Person person);

    String bye(List<String> names);

    String bye(String[] names);

    String byePersons(List<Person> persons);

    String byePersons(Person[] persons);

    @Data
    public static class Person{
        private String name;

        private Long age;

        private Date birth;
    }

    public static void main(String[] args) {
        System.out.println(Person.class.getName());
    }

}

多參數(shù)

    @Test
    public void testMultiParam(){
        result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String","java.lang.Long","java.util.Date"},
                new Object[]{"scj",12L,new Date()});

        System.out.println(result);
    }

POJO

    Map<String,Object> personMap = new HashMap<>();
    {
        personMap.put("name","scj");
        personMap.put("age","12");
        personMap.put("birth",new Date());
    }

    @Test
    public void testPOJO(){

        result = genericService.$invoke(
                "bye",
                new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person"},
                new Object[]{personMap});

        System.out.println(result);
    }

Map

    @Test
    public void testMap(){
        result = genericService.$invoke(
                "bye",
                new String[]{"java.util.Map"},
                new Object[]{personMap});

        System.out.println(result);
    }

集合

    @Test
    public void testList(){
        List<String> names = Lists.newArrayList("scj1","scj2");
        result = genericService.$invoke(
                "bye",
                new String[]{"java.util.List"},
                new Object[]{names});

        System.out.println(result);
    }

數(shù)組

    @Test
    public void testArray(){
        String[] nameArray = new String[]{"scj1","scj3"};
        result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String[]"},
                new Object[]{nameArray});

        System.out.println(result);
    }

集合+POJO

    @Test
    public void testPOJOList(){
        result = genericService.$invoke(
                "byePersons",
                new String[]{"java.util.List"},
                new Object[]{Lists.newArrayList(personMap,personMap)});

        System.out.println(result);
    }

數(shù)組+POJO

    @Test
    public void testPOJOArray(){
        result = genericService.$invoke(
                "byePersons",
                new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person[]"},
                new Object[]{Lists.newArrayList(personMap,personMap)});

        System.out.println(result);
    }

結(jié)果返回

與入?yún)⑾嗨?,雖然$invoke的返回定義為Object,實(shí)際上針對(duì)不同類型有不同的返回。

別想著轉(zhuǎn)換為POJO,你都泛化調(diào)用了,搞不到接口,如何轉(zhuǎn)換。當(dāng)然自己定義一個(gè)完全一樣的當(dāng)然也行。

接口返回類型 $invoke返回類型
基礎(chǔ)類型 基礎(chǔ)類型
POJO HashMap
Collection List返回ArrayList,Set返回HashSet
Array Array
組合類型 根據(jù)上述映射組合返回

原理介紹

消費(fèi)者端

泛化調(diào)用和直接調(diào)用在消費(fèi)者者端,在使用上的區(qū)別是,我們調(diào)用服務(wù)時(shí)使用的接口為GenericService,方法為$invoker。在底層的區(qū)別是,消費(fèi)者端發(fā)出的rpc報(bào)文發(fā)生了變化。

使用方式上的改變

在使用上,不管哪種配置方式,我們都需要配置generic=true

設(shè)置generic=true后,RefereceConfig的interfaceClass會(huì)被強(qiáng)制設(shè)置為GenericService

if (ProtocolUtils.isGeneric(getGeneric())) {
    //如果是泛化調(diào)用
    interfaceClass = GenericService.class;
} else {
    try {
        interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                                       .getContextClassLoader());
    } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
    checkInterfaceAndMethods(interfaceClass, methods);
}

這也使得我們的RefereanceBean返回的是GenericService類型的代理。

invoker = refprotocol.refer(interfaceClass, urls.get(0));

生成的代理是GenericService的代理只是我們使用方式上的變化,更為核心的是,底層發(fā)送的rpc報(bào)文發(fā)生了什么變化。

底層報(bào)文變化

Dubbo的rpc報(bào)文分為header和body兩部分。我們這邊只需要關(guān)注body部分。構(gòu)造邏輯如下

    @Override
    protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
        RpcInvocation inv = (RpcInvocation) data;

        out.writeUTF(version);//dubbo版本號(hào)
        out.writeUTF(inv.getAttachment(Constants.PATH_KEY));//path 就是接口全限定名
        out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));// 接口版本號(hào)

        out.writeUTF(inv.getMethodName());//方法名
        out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));//方法參數(shù)類型
        Object[] args = inv.getArguments();
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                out.writeObject(encodeInvocationArgument(channel, inv, i));//方法參數(shù)
            }
        }
        out.writeObject(RpcUtils.getNecessaryAttachments(inv));//rpc上下文
    }

那么我們通過直接調(diào)用與泛化調(diào)用ByeService的bye方法在報(bào)文上有啥區(qū)別呢?

我一開始以為報(bào)文中的path是GenericeService,其實(shí)并沒有,path就是我們調(diào)用的目標(biāo)方法。

path來源???todo

而報(bào)文中的方法名,方法參數(shù)類型以及具體參數(shù),還是按照GenericeService的$invoke方法入?yún)鬟f的。

這么個(gè)二合一的報(bào)文,發(fā)送到提供者那邊,它估計(jì)也會(huì)很懵逼,我應(yīng)該怎么執(zhí)行?

所以針對(duì)泛化調(diào)用報(bào)文還會(huì)把generic=true放在attchment中傳遞過去

具體邏輯在GenericImplFilter中。

GenericImplFilter中有很多其他邏輯,比如泛化調(diào)用使用的序列化協(xié)議,正常接口走泛化調(diào)用的模式,我們只需要設(shè)置attachment的那部分。

針對(duì)泛化調(diào)用,要進(jìn)行2次序列化/反序列化??聪翽OJO的調(diào)用方式你就知道為啥了

((RpcInvocation) invocation).setAttachment(
    Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));

知道消費(fèi)者端報(bào)文發(fā)生了什么變化,那么接下來就去看提供者端如何處理這個(gè)改造后的報(bào)文。

interfaceClass和interfaceName的區(qū)別

總結(jié)一下ReferenceConfig中interfaceClass和interfaceName的區(qū)別?(這道面試題好像不錯(cuò))

interfaceClass用于指定生成代理的接口
interfaceName用于指定發(fā)送rpc報(bào)文中的path(告訴服務(wù)端我要調(diào)用那個(gè)服務(wù))

提供者端

消費(fèi)者泛化調(diào)用的rpc報(bào)文傳遞到提供者還不能直接使用,雖然path是對(duì)的,但是實(shí)際的方法名,參數(shù)類型,參數(shù)要從rpc報(bào)文的參數(shù)中提取出來。

GenericFilter就是用來做這件事情。

在提供者這邊,針對(duì)泛化調(diào)用的邏輯全部封裝到了GenericFilter,解耦的非常好。

GenericFilter邏輯分析

1. 是否是泛化調(diào)用判斷
if (inv.getMethodName().equals(Constants.$INVOKE)
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())){
    //...
}

注意第4個(gè)條件,一開始很疑惑,后來發(fā)現(xiàn)rpc報(bào)文中的path是目標(biāo)接口的,這邊invoker.getInterface()返回的肯定就是實(shí)際接口了

2. 方法參數(shù)提取
//從argument提取目標(biāo)方法名 方法類型 方法參數(shù)
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];
3. 方法參數(shù)解析,進(jìn)一步反序列化
//反射獲取目標(biāo)執(zhí)行方法
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
if (args == null) {
    args = new Object[params.length];
}
String generic = inv.getAttachment(Constants.GENERIC_KEY);

if (StringUtils.isBlank(generic)) {
    generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
}

//一些反序列化
if (StringUtils.isEmpty(generic)
    || ProtocolUtils.isDefaultGenericSerialization(generic)) {
    args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
    for (int i = 0; i < args.length; i++) {
        if (byte[].class == args[i].getClass()) {
            try {
                UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
                args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
                    .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
                    .deserialize(null, is).readObject();
            } catch (Exception e) {
                throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
            }
        } else {
            throw new RpcException(
                "Generic serialization [" +
                Constants.GENERIC_SERIALIZATION_NATIVE_JAVA +
                "] only support message type " +
                byte[].class +
                " and your message type is " +
                args[i].getClass());
        }
    }
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
    for (int i = 0; i < args.length; i++) {
        if (args[i] instanceof JavaBeanDescriptor) {
            args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
        } else {
            throw new RpcException(
                "Generic serialization [" +
                Constants.GENERIC_SERIALIZATION_BEAN +
                "] only support message type " +
                JavaBeanDescriptor.class.getName() +
                " and your message type is " +
                args[i].getClass().getName());
        }
    }
}

這邊有個(gè)疑問,為什么這邊還要再次反序列化一次,netty不是有decoder么??

嗯,你別忘了,針對(duì)一個(gè)POJO你傳過來是一個(gè)Map,從Map轉(zhuǎn)換為POJO需要這邊進(jìn)一步處理。

4. 調(diào)用目標(biāo)服務(wù)
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));

這邊的invoker就是實(shí)際服務(wù)提供者的invoker,因?yàn)槲覀兊膒ath是正確的,invoker獲取在DubboProtocl的requestHandler回調(diào)中

5. 異常處理
if (result.hasException()
    && !(result.getException() instanceof GenericException)) {
    return new RpcResult(new GenericException(result.getException()));
}

這邊需要注意一下??!針對(duì)接口的泛化調(diào)用,拋出的異常都會(huì)經(jīng)過GenericException包裝一下。

總結(jié)

從功能上來看,泛化調(diào)用提供了在沒有接口依賴情況下進(jìn)行的解決方案,豐富框架的使用場(chǎng)景。
從設(shè)計(jì)上來看,泛化調(diào)用的功能還是通過擴(kuò)展的方式實(shí)現(xiàn)的,侵入性不強(qiáng),值得學(xué)習(xí)借鑒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容