dubbo泛化調(diào)用使用及原理示例解析
什么是泛化調(diào)用
本文案例代碼見 git@github.com:shengchaojie/dubbo_best_practise.git
通常我們想調(diào)用別人的dubbo服務(wù)時,我們需要在項目中引入對應(yīng)的jar包。而泛化調(diào)用的作用是,我們無需依賴相關(guān)jar包,也能調(diào)用到該服務(wù)。
這個特性一般使用在網(wǎng)關(guān)類項目中,在業(yè)務(wù)開發(fā)中基本不會使用。
使用方式
假設(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的三個參數(shù)分別為,方法名,方法參數(shù)類型數(shù)組,方法參數(shù)數(shù)組。
方法入?yún)?gòu)造
可以看到泛化調(diào)用的一個復(fù)雜性在于$invoke的第三個參數(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,實際上針對不同類型有不同的返回。
別想著轉(zhuǎn)換為POJO,你都泛化調(diào)用了,搞不到接口,如何轉(zhuǎn)換。當(dāng)然自己定義一個完全一樣的當(dāng)然也行。
接口返回類型 | $invoke返回類型 |
---|---|
基礎(chǔ)類型 | 基礎(chǔ)類型 |
POJO | HashMap |
Collection | List返回ArrayList,Set返回HashSet |
Array | Array |
組合類型 | 根據(jù)上述映射組合返回 |
原理介紹
消費者端
泛化調(diào)用和直接調(diào)用在消費者者端,在使用上的區(qū)別是,我們調(diào)用服務(wù)時使用的接口為GenericService,方法為$invoker。在底層的區(qū)別是,消費者端發(fā)出的rpc報文發(fā)生了變化。
使用方式上的改變
在使用上,不管哪種配置方式,我們都需要配置generic=true
設(shè)置generic=true后,RefereceConfig的interfaceClass會被強(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報文發(fā)生了什么變化。
底層報文變化
Dubbo的rpc報文分為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版本號 out.writeUTF(inv.getAttachment(Constants.PATH_KEY));//path 就是接口全限定名 out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));// 接口版本號 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方法在報文上有啥區(qū)別呢?
我一開始以為報文中的path是GenericeService,其實并沒有,path就是我們調(diào)用的目標(biāo)方法。
path來源???todo
而報文中的方法名,方法參數(shù)類型以及具體參數(shù),還是按照GenericeService的$invoke方法入?yún)鬟f的。
這么個二合一的報文,發(fā)送到提供者那邊,它估計也會很懵逼,我應(yīng)該怎么執(zhí)行?
所以針對泛化調(diào)用報文還會把generic=true放在attchment中傳遞過去
具體邏輯在GenericImplFilter中。
GenericImplFilter中有很多其他邏輯,比如泛化調(diào)用使用的序列化協(xié)議,正常接口走泛化調(diào)用的模式,我們只需要設(shè)置attachment的那部分。
針對泛化調(diào)用,要進(jìn)行2次序列化/反序列化??聪翽OJO的調(diào)用方式你就知道為啥了
((RpcInvocation) invocation).setAttachment( Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
知道消費者端報文發(fā)生了什么變化,那么接下來就去看提供者端如何處理這個改造后的報文。
interfaceClass和interfaceName的區(qū)別
總結(jié)一下ReferenceConfig中interfaceClass和interfaceName的區(qū)別?(這道面試題好像不錯)
interfaceClass用于指定生成代理的接口
interfaceName用于指定發(fā)送rpc報文中的path(告訴服務(wù)端我要調(diào)用那個服務(wù))
提供者端
消費者泛化調(diào)用的rpc報文傳遞到提供者還不能直接使用,雖然path是對的,但是實際的方法名,參數(shù)類型,參數(shù)要從rpc報文的參數(shù)中提取出來。
GenericFilter就是用來做這件事情。
在提供者這邊,針對泛化調(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個條件,一開始很疑惑,后來發(fā)現(xiàn)rpc報文中的path是目標(biāo)接口的,這邊invoker.getInterface()返回的肯定就是實際接口了
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()); } } }
這邊有個疑問,為什么這邊還要再次反序列化一次,netty不是有decoder么??
嗯,你別忘了,針對一個POJO你傳過來是一個Map,從Map轉(zhuǎn)換為POJO需要這邊進(jìn)一步處理。
4. 調(diào)用目標(biāo)服務(wù)
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
這邊的invoker就是實際服務(wù)提供者的invoker,因為我們的path是正確的,invoker獲取在DubboProtocl的requestHandler回調(diào)中
5. 異常處理
if (result.hasException() && !(result.getException() instanceof GenericException)) { return new RpcResult(new GenericException(result.getException())); }
這邊需要注意一下??!針對接口的泛化調(diào)用,拋出的異常都會經(jīng)過GenericException包裝一下。
總結(jié)
從功能上來看,泛化調(diào)用提供了在沒有接口依賴情況下進(jìn)行的解決方案,豐富框架的使用場景。
從設(shè)計上來看,泛化調(diào)用的功能還是通過擴(kuò)展的方式實現(xiàn)的,侵入性不強(qiáng),值得學(xué)習(xí)借鑒。
以上就是dubbo泛化調(diào)用使用及原理示例解析的詳細(xì)內(nèi)容,更多關(guān)于dubbo泛化調(diào)用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中mybatis關(guān)于example類的使用詳解
這篇文章主要介紹了Java中mybatis中關(guān)于example類的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07使用Spring方法攔截器MethodInterceptor
這篇文章主要介紹了使用Spring方法攔截器MethodInterceptor,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java編程實現(xiàn)五子棋人人對戰(zhàn)代碼示例
這篇文章主要介紹了Java編程實現(xiàn)五子棋人人對戰(zhàn)代碼示例,具有一定借鑒價值,需要的朋友可以參考下。2017-11-11在?Java?中將Object?轉(zhuǎn)換為?Int的四種方法
這篇文章主要介紹了在Java中如何將?Object?轉(zhuǎn)換為Int,本文研究了在?Java中將Object轉(zhuǎn)換為int的四種不同方法,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05java返回集合為null還是空集合及空集合的三種寫法小結(jié)
這篇文章主要介紹了java返回集合為null還是空集合及空集合的三種寫法小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11