柚子快報激活碼778899分享:Dubbo泛化調用原理及使用
柚子快報激活碼778899分享:Dubbo泛化調用原理及使用
目錄
引言
什么是泛化調用
Dubbo泛化調用(客戶端泛化)
通過 Spring XML 配置進行泛化調用
通過 API 編程進行泛化調用
參數(shù)或返回值是 POJO 的場景
總結一下泛化調用的三部曲
Dubbo泛化實現(xiàn)(服務端泛化)
服務端實現(xiàn) GenericService
服務端暴露服務
源碼實現(xiàn)
GenericImplFilter
GenericFilter
引言
????????在日常工作中,我們在編寫完一個dubbo接口后,經常會遇到dubbo接口的調試問題,之前我們一般的處理方法是再寫一個http接口去透明化調用dubbo接口,這樣就可以在網頁或者postman等接口調試工具去調試這個dubbo接口。但是這種方法耗時耗力,這時,Dubbo提供的泛化調用(Generic Invocation)機制就能派上用場。本文將詳細解析Dubbo泛化調用的使用及原理。
什么是泛化調用
????????Dubbo 泛化調用是一種在分布式服務框架 Dubbo 中實現(xiàn)的服務調用機制,它允許服務消費者在沒有服務提供者接口定義的情況下進行服務調用。這種機制特別適用于以下場景:
透傳式調用,發(fā)起方只是想調用提供者拿到結果,沒有過多的業(yè)務邏輯訴求,即使有,也是拿到結果后再繼續(xù)做分發(fā)處理。 代理服務,所有的請求都會經過代理服務器,而代理服務器不會感知任何業(yè)務邏輯,只是一個通道,接收數(shù)據(jù) -> 發(fā)起調用 -> 返回結果,調用流程非常簡單純粹。 前端網關,有些內網環(huán)境的運營頁面,對 URL 的格式沒有那么嚴格的講究,頁面的功能都是和后端服務一對一的操作,非常簡單直接。
????????通俗地講,泛化可以理解為采用一種統(tǒng)一的方式來發(fā)起對任何服務方法的調用,至少我們知道是一種接口調用的方式,只是這種方式有一個比較獨特的名字而已。
Dubbo泛化調用(客戶端泛化)
????????泛化調用 :要在服務消費端沒有API接口類及模型類元(比如入?yún)⒑统鰠⒌腜OJO 類)的情況下使用。在進行服務調用時相關參數(shù)通過 Map 形式將數(shù)據(jù)傳遞,由服務提供者將 Map 轉換為 實體類,再進行調用。
????????簡單來說 ,泛化調用即服務消費者端啟用了泛化調用,而服務提供者端則是正常服務。
通過 Spring XML 配置進行泛化調用
????????在 Spring 配置申明 generic="true",如:
????????需要使用的地方,通過強制類型轉化為 GenericService 進行調用:
GenericService userService = (GenericService) context.getBean("userService");
// primary param and return value
String name = (String) userService.$invoke("delete", new String[]{int.class.getName()}, new Object[]{1});
System.out.println(name);
??其中:
GenericService 這個接口只有一個方法,名為 $invoke,它接受三個參數(shù),分別為方法名、方法參數(shù)類型數(shù)組和參數(shù)值數(shù)組; 對于方法參數(shù)類型數(shù)組:
如果是基本類型,如 int 或 long,可以使用 int.class.getName()獲取其類型; 如果是基本類型數(shù)組,如 int[],則可以使用 int[].class.getName(); 如果是 POJO,則直接使用全類名,如 com.alibaba.dubbo.samples.generic.api.Params。
通過 API 編程進行泛化調用
ApplicationConfig application = new ApplicationConfig();
application.setName("api-generic-consumer");
//注冊中心
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registry);
ReferenceConfig
// 弱類型接口名
reference.setInterface("com.alibaba.dubbo.samples.generic.api.IUserService");
// 聲明為泛化接口
reference.setGeneric(true);
reference.setApplication(application);
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
// 泛化調用核心方法
String name = (String) genericService.$invoke("delete", new String[]{int.class.getName()}, new Object[]{1});
System.out.println(name);
????????通過 API 的方式,不需要像 XML 的方式需要提前將服務配置好,可以動態(tài)構建 ReferenceConfig;相對 XML 來說,API 的方式更常見。
參數(shù)或返回值是 POJO 的場景
????????比如方法簽名是 User get(Params params);其中 User 有 id 和 name 兩個屬性,Params 有 query 一個屬性。
????????以下是消費端的調用代碼:
String[] parameterTypes = new String[]{"com.alibaba.dubbo.samples.generic.api.Params"};
Map param.put("class", "com.alibaba.dubbo.samples.generic.api.Params"); param.put("query", "a=b"); Object user = userService.$invoke("get", parameterTypes, new Object[]{param}); System.out.println("sample one result: " + user); ????????上述代碼的輸出結果為: sample one result: {name=charles, id=1, class=com.alibaba.dubbo.samples.generic.api.User} ????????這里,Dubbo 框架會自動將 POJO 的返回值轉換成 Map??梢钥吹?,返回值 user 是一個 HashMap,里面分別存放了 name、id、class 三個 k/v。 總結一下泛化調用的三部曲 接口類名、接口方法名、接口方法參數(shù)類名、業(yè)務請求參數(shù),四個維度的數(shù)據(jù)不能少。 根據(jù)接口類名創(chuàng)建 ReferenceConfig 對象,設置 generic = true 屬性,調用 referenceConfig.get 拿到 genericService 泛化對象。 傳入接口方法名、接口方法參數(shù)類名、業(yè)務請求參數(shù),調用 genericService.$invoke 方法拿到響應對象,并判斷響應成功或失敗,然后完成數(shù)據(jù)最終返回。 Dubbo泛化實現(xiàn)(服務端泛化) ????????泛化接口實現(xiàn)主要用于服務提供端沒有API接口類及模型類元(比如入?yún)⒑统鰠⒌腜OJO 類)的情況下使用。消費者發(fā)起接口請求時需要將相關信息轉換為 Map 傳遞給 提供者,由提供者根據(jù)信息找到對應的泛型實現(xiàn)來進行處理。 ????????簡單來說 ,泛化實現(xiàn)即服務提供者端啟用了泛化實現(xiàn),而服務消費者端則是正常調用。 服務端實現(xiàn) GenericService public class GenericServiceImpl implements GenericService { @Override public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException { if (method.equals("hi")) { return "hi, " + args[0]; } return "welcome"; } } 服務端暴露服務 ApplicationConfig application = new ApplicationConfig(); application.setName("api-generic-provider"); // 注冊中心 RegistryConfig registry = new RegistryConfig(); registry.setAddress("zookeeper://127.0.0.1:2181"); application.setRegistry(registry); // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口實現(xiàn) GenericService genericService = new GenericServiceImpl(); // 暴露服務 ServiceConfig service2.setApplication(application); // 弱類型接口名 service2.setInterface("com.alibaba.dubbo.samples.generic.api.HiService"); // 指向一個通用服務實現(xiàn) service2.setRef(genericService); service2.export(); ????????以上代碼是使用API編程,也可以使用 XML 配置的方式暴露服務: 源碼實現(xiàn) Dubbo 泛化調用和泛化實現(xiàn)依賴于下面兩個過濾器來完成。如下圖: GenericImplFilter:完成了消費者端的泛化功能。 GenericFilter:完成了提供者端的泛化功能。 GenericImplFilter ????????當消費者進行調用的是泛化實現(xiàn)時,會將參數(shù)信息按照指定的序列化方式進行序列化后進行泛化調用。(這里會將調用方法指定為 $invoke,因為 GenericFilter 中判斷是否是泛化調用的條件之一就是 方法名為 $invoke) ????????當消費者進行泛化調用時,會將參數(shù)信息進行序列化后進行泛化調用。 /** * GenericImplInvokerFilter */ @Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000) public class GenericImplFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class); // 泛化調用的參數(shù)類型 private static final Class>[] GENERIC_PARAMETER_TYPES = new Class>[]{String.class, String[].class, Object[].class}; @Override public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException { String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY); // 1. 判斷服務端是否是泛化實現(xiàn) // generic 滿足三種泛化情況之一 && 調用方法名不為 $invoke && 參數(shù)類型為 RpcInvocation if (ProtocolUtils.isGeneric(generic) && !Constants.$INVOKE.equals(invocation.getMethodName()) && invocation instanceof RpcInvocation) { // 1.1 獲取泛化調用的參數(shù) :調用方法名、調用參數(shù)類型、調用參數(shù)值等 RpcInvocation invocation2 = (RpcInvocation) invocation; String methodName = invocation2.getMethodName(); Class>[] parameterTypes = invocation2.getParameterTypes(); Object[] arguments = invocation2.getArguments(); String[] types = new String[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { types[i] = ReflectUtils.getName(parameterTypes[i]); } Object[] args; // 1.2 判斷序列化方式,進行序列化 // 如果是 Bean 序列化方式,則使用JavaBeanSerializeUtil 進行序列化 if (ProtocolUtils.isBeanGenericSerialization(generic)) { args = new Object[arguments.length]; for (int i = 0; i < arguments.length; i++) { args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD); } } else { // 否則(generic = true || nativejava) 使用PojoUtils 進行序列化 args = PojoUtils.generalize(arguments); } // 設置調用方法為 $invoke、參數(shù)類型為GENERIC_PARAMETER_TYPES,并設置參數(shù)具體值。 // 目的是為了讓 GenericFilter 能識別出這次調用是泛化調用。 invocation2.setMethodName(Constants.$INVOKE); invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES); invocation2.setArguments(new Object[]{methodName, types, args}); // 1.3 進行泛化調用 Result result = invoker.invoke(invocation2); // 1.4 如果泛化調用沒有異常, 則將結果集反序列化后返回。 if (!result.hasException()) { Object value = result.getValue(); try { Method method = invoker.getInterface().getMethod(methodName, parameterTypes); // 對結果進行反序列化 if (ProtocolUtils.isBeanGenericSerialization(generic)) { if (value == null) { return new RpcResult(value); } else if (value instanceof JavaBeanDescriptor) { return new RpcResult(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value)); } else { throw new RpcException( "The type of result value is " + value.getClass().getName() + " other than " + JavaBeanDescriptor.class.getName() + ", and the result is " + value); } } else { return new RpcResult(PojoUtils.realize(value, method.getReturnType(), method.getGenericReturnType())); } } catch (NoSuchMethodException e) { throw new RpcException(e.getMessage(), e); } } else if (result.getException() instanceof GenericException) { // 返回異常是 GenericException 類型,則說明是泛化異常而非調用過程中異常。進行處理 GenericException exception = (GenericException) result.getException(); try { String className = exception.getExceptionClass(); Class> clazz = ReflectUtils.forName(className); Throwable targetException = null; Throwable lastException = null; try { targetException = (Throwable) clazz.newInstance(); } catch (Throwable e) { lastException = e; for (Constructor> constructor : clazz.getConstructors()) { try { targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]); break; } catch (Throwable e1) { lastException = e1; } } } if (targetException != null) { try { Field field = Throwable.class.getDeclaredField("detailMessage"); if (!field.isAccessible()) { field.setAccessible(true); } field.set(targetException, exception.getExceptionMessage()); } catch (Throwable e) { logger.warn(e.getMessage(), e); } result = new RpcResult(targetException); } else if (lastException != null) { throw lastException; } } catch (Throwable e) { throw new RpcException("Can not deserialize exception " + exception.getExceptionClass() + ", message: " + exception.getExceptionMessage(), e); } } return result; } // 2. 判斷消費者是否開啟了泛化調用 // 調用方法名為 $invoke && invocation參數(shù)有三個 && generic 參數(shù)滿足三種泛化方式之一 if (invocation.getMethodName().equals(Constants.$INVOKE) && invocation.getArguments() != null && invocation.getArguments().length == 3 && ProtocolUtils.isGeneric(generic)) { // 2.1 序列化參數(shù) Object[] args = (Object[]) invocation.getArguments()[2]; if (ProtocolUtils.isJavaGenericSerialization(generic)) { for (Object arg : args) { if (!(byte[].class == arg.getClass())) { // 拋出 RpcException 異常 error(generic, byte[].class.getName(), arg.getClass().getName()); } } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { for (Object arg : args) { if (!(arg instanceof JavaBeanDescriptor)) { // 拋出 RpcException 異常 error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName()); } } } // 設置參數(shù) ((RpcInvocation) invocation).setAttachment( Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY)); } // 進行序列化調用并返回 return invoker.invoke(invocation); } } ????????代碼點1:該分支是泛化實現(xiàn),如果是泛化實現(xiàn),則根據(jù)generic的值進行序列化,然后調用$invoke方法,因為服務端實現(xiàn)為泛化實現(xiàn),所有的服務提供者實現(xiàn)GenericeServer#$invoker方法,其實現(xiàn)方式就是將Bean轉換成Map。 ????????代碼點2:泛化引用,調用方是直接通過GenericService#$invoke方法進行調用,以此來區(qū)分是泛化調用還是泛化引用,那不經要問,為什么invoker.getUrl().getParameter(Constants.GENERIC_KEY)中獲取的generic參數(shù)到底是< dubbo:service/>中配置的還是< dubbo:reference/>中配置的呢?其實不難理解: ????????dubbo:servcie未配置而dubbo:reference配置了,則代表的是消費端的,必然是泛化調用。 ????????dubbo:servcie配置而dubbo:reference未配置了,則代表的是服務端的,必然是泛化實現(xiàn)。 ????????如果兩者都配置了,generic以消費端為主。消費端參數(shù)與服務端參數(shù)的合并在服務發(fā)現(xiàn)時,注冊中心首先會將服務提供者的URL通知消費端,然后消費端會使用當前的配置與服務提供者URL中的配置進行合并,如遇到相同參數(shù),則消費端覆蓋服務端。 GenericFilter ????????GenericFilter 作用于提供者。在 GenericImplFilter 中我們知道,一旦Dubbo確定了是泛化調用或提供者時泛化實現(xiàn)時就會將參數(shù)序列化,所以 GenericFilter 判斷如果是泛化操作第一步則是按照序列化方式進行反序列化,并進行服務調用。 @Activate(group = Constants.PROVIDER, order = -20000) public class GenericFilter implements Filter { @Override public Result invoke(Invoker> invoker, Invocation inv) throws RpcException { // 1. 消費者進行泛化調用 // 調用方法為 $invoke && 參數(shù)有三個 && 調用接口不是 GenericService if (inv.getMethodName().equals(Constants.$INVOKE) && inv.getArguments() != null && inv.getArguments().length == 3 && !GenericService.class.isAssignableFrom(invoker.getInterface())) { // 1.1 參數(shù)解析 // 調用方法名 String name = ((String) inv.getArguments()[0]).trim(); // 調用方法參數(shù)類型 String[] types = (String[]) inv.getArguments()[1]; // 調用參數(shù)值 Object[] args = (Object[]) inv.getArguments()[2]; try { // 通過反射獲取方法實例 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); } // generic 為空 || 默認情況,則使用 generic=true 的方式 if (StringUtils.isEmpty(generic) || ProtocolUtils.isDefaultGenericSerialization(generic)) { args = PojoUtils.realize(args, params, method.getGenericParameterTypes()); } else if (ProtocolUtils.isJavaGenericSerialization(generic)) { // generic = nativjava 的方式 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)) { // generic = bean的方式 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()); } } } // 進行服務調用。這里會先傳遞給下一個 filter,最后進行服務調用 Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments())); // 對結果集進行反序列化并返回 if (result.hasException() && !(result.getException() instanceof GenericException)) { return new RpcResult(new GenericException(result.getException())); } if (ProtocolUtils.isJavaGenericSerialization(generic)) { try { UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512); ExtensionLoader.getExtensionLoader(Serialization.class) .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA) .serialize(null, os).writeObject(result.getValue()); return new RpcResult(os.toByteArray()); } catch (IOException e) { throw new RpcException("Serialize result failed.", e); } } else if (ProtocolUtils.isBeanGenericSerialization(generic)) { return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD)); } else { return new RpcResult(PojoUtils.generalize(result.getValue())); } } catch (NoSuchMethodException e) { throw new RpcException(e.getMessage(), e); } catch (ClassNotFoundException e) { throw new RpcException(e.getMessage(), e); } } // 2. 常規(guī)調用 return invoker.invoke(inv); } } ????????代碼點1:如果方法名為$invoker,并且只有3個參數(shù),并且服務端實現(xiàn)為非返回實現(xiàn),則認為本次服務調用時客戶端泛化引用服務端,客戶端的泛化調用,需要將請求參數(shù)反序列化為該接口真實的pojo對象。 ????????代碼點2:根據(jù)接口名(API類)、方法名、方法參數(shù)類型列表,根據(jù)反射機制獲取對應的方法。 ????????代碼點3:處理普通的泛化引用調用,即處理 ????????代碼點4:處理< dubbo:reference generic=“nativejava” /> 啟用泛化引用,并使用nativejava序列化參數(shù),在服務端這邊通過nativejava反序列化參數(shù)成pojo對象。 ????????代碼點5:處理< dubbo:reference generic=“bean” /> 啟用泛化引用,并使用javabean序列化參數(shù),在服務端這邊通過javabean反序列化參數(shù)成pojo對象。 ????????代碼點6:序列化API方法中聲明的類型,構建new RpcInvocation(method, args, inv.getAttachments())調用環(huán)境,繼續(xù)調用后續(xù)過濾器。 ????????代碼點7:處理執(zhí)行結果,如果是nativejava或bean,則需要對返回結果序列化,如果是generic=true,則使用PojoUtils.generalize序列化,也即將pojo序列化為Map。 柚子快報激活碼778899分享:Dubbo泛化調用原理及使用 相關閱讀
本文內容根據(jù)網絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。