面試官從Dubbo泛化調(diào)用問到設(shè)計模式,我們聊了三十分鐘
JAVA前線
歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)
1 泛化調(diào)用實例
對于JAVA服務(wù)端開發(fā)者而言在使用Dubbo時并不經(jīng)常使用泛化調(diào)用,通常方法是在生產(chǎn)者發(fā)布服務(wù)之后,消費(fèi)者可以通過引入生產(chǎn)者提供的client進(jìn)行調(diào)用。那么泛化調(diào)用使用場景是什么呢?
第一種場景是消費(fèi)者不希望引入生產(chǎn)者提供的client依賴,只希望關(guān)注調(diào)用哪個方法,需要傳什么參數(shù)即可。第二種場景是消費(fèi)者不是使用Java語言,而是使用例如Python語言,那么如何調(diào)用使用Java語言生產(chǎn)者提供的服務(wù)呢?這時我們可以選擇泛化調(diào)用。
泛化調(diào)用使用方法并不復(fù)雜,下面我們編寫一個泛化調(diào)用實例。首先生產(chǎn)者發(fā)布服務(wù),這與普通服務(wù)發(fā)布沒有任何區(qū)別。
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文件內(nèi)容:
<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">
<!-- 提供方應(yīng)用信息,用于計算依賴關(guān)系 -->
<dubbo:application name="java-front-provider" />
<!-- 連接注冊中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 生產(chǎn)者9999在端口暴露服務(wù) -->
<dubbo:protocol name="dubbo" port="9999" />
<!-- Bean -->
<bean id="helloService" class="com.java.front.dubbo.demo.provider.HelloServiceImpl" />
<!-- 暴露服務(wù) -->
<dubbo:service interface="com.java.front.dubbo.demo.provider.HelloService" ref="helloService" />
</beans>
消費(fèi)者代碼有所不同:
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<String, Object> person = new HashMap<String, Object>();
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
我們通過源碼分析講解泛化調(diào)用原理,我們首先需要了解Invoker這個Dubbo重量級概念。在生產(chǎn)者暴露服務(wù)流程總體分為兩步,第一步是接口實現(xiàn)類轉(zhuǎn)換為Invoker,第二步是Invoker轉(zhuǎn)換為Exporter并放入ExporterMap,我們看看生產(chǎn)者暴露服務(wù)流程圖:

生產(chǎn)者通過ProxyFactory.getInvoker方法創(chuàng)建Invoker(AbstractProxyInvoker):
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);
}
};
}
}
消費(fèi)者引用服務(wù)流程圖:

消費(fèi)者Invoker通過顯示實例化創(chuàng)建,例如本地暴露和遠(yuǎn)程暴露都是通過顯示初始化的方法創(chuàng)建Invoker(AbstractInvoker):
new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap)
new DubboInvoker<T>(serviceType, url, getClients(url), invokers)
再通過ProxyFactory.getProxy創(chuàng)建代理:
public class JdkProxyFactory extends AbstractProxyFactory {
@Override
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
InvokerInvocationHandler invokerInvocationHandler = new InvokerInvocationHandler(invoker);
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, invokerInvocationHandler);
}
}
無論是生產(chǎn)者還是消費(fèi)者的Invoker都實現(xiàn)自org.apache.dubbo.rpc.Invoker:
public abstract class AbstractInvoker<T> implements Invoker<T>{
}
public abstract class AbstractProxyInvoker<T> implements Invoker<T> {
}
3 裝飾器模式
為什么生產(chǎn)者和消費(fèi)者都要轉(zhuǎn)換為Invoker而不是不直接調(diào)用呢?我認(rèn)為Invoker正是Dubbo設(shè)計精彩之處:真實調(diào)用都轉(zhuǎn)換為Invoker,Dubbo就可以通過裝飾器模式增強(qiáng)Invoker功能。我們看看什么是裝飾器模式。
裝飾器模式可以動態(tài)將責(zé)任附加到對象上,在不改變原始類接口情況下,對原始類功能進(jìn)行增強(qiáng),并且支持多個裝飾器的嵌套使用。實現(xiàn)裝飾器模式需要以下組件:
(1) Component(抽象構(gòu)件)
核心業(yè)務(wù)抽象:可以使用接口或者抽象類
(2) ConcreteComponent(具體構(gòu)件)
實現(xiàn)核心業(yè)務(wù):最終執(zhí)行的業(yè)務(wù)代碼
(3) Decorator(抽象裝飾器)
抽象裝飾器類:實現(xiàn)Component并且組合一個Component對象
(4) ConcreteDecorator(具體裝飾器)
具體裝飾內(nèi)容:裝飾核心業(yè)務(wù)代碼
我們分析一個裝飾器實例。有一名足球運(yùn)動員要去踢球,我們用球鞋和球襪為他裝飾一下,這樣可以使戰(zhàn)力值增加。
(1) Component
/**
* 抽象構(gòu)件(可以用接口替代)
*/
public abstract class Component {
/**
* 踢足球(業(yè)務(wù)核心方法)
*/
public abstract void playFootBall();
}
(2) ConcreteComponent
/**
* 具體構(gòu)件
*/
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("換上球襪戰(zhàn)力值增加");
}
/**
* 重寫父類方法
*/
@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("換上球鞋戰(zhàn)力值增加");
}
/**
* 重寫父類方法
*/
@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();
}
}
// 換上球鞋戰(zhàn)力值增加
// 換上球襪戰(zhàn)力值增加
// 球員踢球
4 過濾器鏈路
Dubbo為Invoker增強(qiáng)了哪些功能?過濾器鏈?zhǔn)俏艺J(rèn)為增強(qiáng)的最重要的功能之一,我們繼續(xù)分析源碼:
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;
}
}
無論是生產(chǎn)者還是消費(fèi)者都會創(chuàng)建過濾器鏈,我們看看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)根據(jù)group過濾得到過濾器列表
// (3)Invoker最終被放到過濾器鏈尾部
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
// 構(gòu)造一個簡化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 {
// 構(gòu)造過濾器鏈路
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注解的過濾器,根據(jù)group過濾得到過濾器列表,Invoker最終被放到過濾器鏈尾部,生產(chǎn)者最終生成鏈路:
EchoFilter -> ClassloaderFilter -> GenericFilter -> ContextFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter -> ExceptionFilter -> AbstractProxyInvoker
消費(fèi)者最終生成鏈路:
ConsumerContextFilter -> FutureFilter -> MonitorFilter -> GenericImplFilter -> DubboInvoker
5 泛化調(diào)用原理
我們終于即將看到泛化調(diào)用核心原理,我們在生產(chǎn)者鏈路看到GenericFilter過濾器,消費(fèi)者鏈路看到GenericImplFilter過濾器,正是這兩個過濾器實現(xiàn)了泛化調(diào)用。
(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)) {
// 第一個參數(shù)表示方法名
// 第二個參數(shù)表示參數(shù)類型
// 第三個參數(shù)表示參數(shù)值 -> [{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());
}
}
}
// 附加參數(shù)generic值設(shè)置為true
((RpcInvocation) invocation).setAttachment(Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
}
// 繼續(xù)執(zhí)行過濾器鏈路
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 文章總結(jié)
本文首先介紹了如何使用泛化調(diào)用,并引出泛化調(diào)用為什么生效這個問題。第二點(diǎn)介紹了重量級概念I(lǐng)nvoker,并引出為什么Dubbo要創(chuàng)建Invoker這個問題。第三點(diǎn)介紹了裝飾器模式如何增強(qiáng)功能。最后我們通過源碼分析知道了過濾器鏈增強(qiáng)了Invoker功能并且是實現(xiàn)泛化調(diào)用的核心,希望本文對大家有所幫助。
JAVA前線
歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)
