服务引用流程图
下图是Dubbo框架服务引用的整体流程图:
Dubbo框架通过<dubbo:reference>
标签开启一个服务引用,例如:<dubbo:reference id="userService" interface="com.luke.dubbo.api.service.UserService" />
,服务引用的目的是为引用服务生成一个远程代理对象,这样可以像使用一个本地Bean一样去使用远程服务接口,对用户屏蔽掉远程调用和处理过程。
服务引用的整体流程可以分为两步骤,第一步通过RegistryProtocol将引用的服务接口Class生成Invoker,如果注册中心是多实例,那么将生成多个Invoker(一个注册中心生成一个Invoker),接着通过Cluster转换成一个Invoker;第二步是通过ProxyFactory(代理工厂)将Invoker转换成最终的代理对象(InvokerInvocationHandler)。
源码解析服务引用流程
Dubbo框架中一个服务引用就会对应有一个ReferenceBean,该类继承了ReferenceConfig并且实现了FactoryBean接口,所以ReferenceBean将作为Dubbo服务接口的工厂Bean(可以生成代理对象),即通过利用了Spring的FactoryBean机制,当我们代码引用到服务接口时,Spring框架会回调ReferenceBean的getObject()方法。
Spring的FactoryBean机制:https://www.jianshu.com/p/f4dca40c55c1
紧接着代码会依次执行ReferenceConfig类三个方法get()-->init()-->createProxy(map)
private T createProxy(Map<String, String> map) {
//......省略部分代理
//加载注册中心地址
List<URL> us = loadRegistries(false);//@1
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
//调用过程上传到监控中心
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));//@2
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.size() == 1) {//只有一个注册中心实例
invoker = refprotocol.refer(interfaceClass, urls.get(0));//@3
} else {//多个注册中心实例
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
//添加多个Invoker
invokers.add(refprotocol.refer(interfaceClass, url));//@4
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; //使用最后一个注册中心URL
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
//通过Cluster将多个Invoker转换成一个Invoker
invoker = cluster.join(new StaticDirectory(u, invokers));//@5
}
}
//创建服务代理对象
return (T) proxyFactory.getProxy(invoker);//@6
}
@1:加载出所有注册中心的地址。
@2:如果配置有监控中心,调用过程将上传到监控中心。
@3:执行protocol.refer()方法为服务接口生成Invoker对象。
@4:在多注册中心情况下,将多Invoker存储到invokers集合。
@5:在多注册中心情况下,通过通过Cluster将多个Invoker转换成一个Invoker。
@6:代理工厂ProxyFactory利用Invoker创建服务代理对象。
创建Invoker实例
接下来我们解析refprotocol.refer(Class<T> type, URL url)
,通过该方法可以获取到Invoker实例,refprotocol是一个动态代理对象Protocol$Adaptive,目标对象是RegistryProtocol。
之前我们说过Protocol有包装扩展类,所以执行顺序是:ProtocolFilterWrapper.refer()-->QosProtocolWarpper.refer()-->ProtocolListenerWrappeer.refer()-->RegistryProtocol.refer()
其中QosProtocolWarpper.refer()负责启动QoS服务,QoS是Dubbo的在线运维命令,可以对服务进行动态的配置、控制及查询。
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
//启动QoS服务
startQosServer(url);
return protocol.refer(type, url);
}
return protocol.refer(type, url);
}
进入到RegistryProtocol.refer()方法:
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//从url参数里面获取registry参数值,更新protocol属性
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY,
Constants.DEFAULT_REGISTRY)).
removeParameter(Constants.REGISTRY_KEY);//@1
//通过注册工厂获取注册中心实例(连接zookeeper)
Registry registry = registryFactory.getRegistry(url);//@2
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
//执行doRefer方法
return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//创建RegistryDirectory实例
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);//@3
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
//执行注册操作(在consumer节点下添加ULR临时节点)
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY,
Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));//@4
}
//执行订阅操作(订阅providers、configurators、routers三个节点)
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));//@5
Invoker invoker = cluster.join(directory);//@6
ProviderConsumerRegTable.registerConsumer(invoker,
url, subscribeUrl, directory);//@7
return invoker;
}
@1:从url参数里面获取registry参数值,更新protocol属性,值为zookeeper。
@2:通过注册工厂获取注册中心实例ZookeeperRegistry,实例化过程将发起连接Zookeeper注册中心。
@3:创建RegistryDirectory实例。RegistryDirectory实现了NotifyListener,负责管理本地缓存服务提供者、配置、路由规则的变化。
@4:执行注册操作,最终会执行ZookeeperRegistry.doRegister()
方法,创建URL临时节点。
@Override
protected void doRegister(URL url) {
//在consumers节点下添加ULR临时节点。
zkClient.create(toUrlPath(url),
url.getParameter(Constants.DYNAMIC_KEY, true));
}
@5:执行订阅操作,订阅providers、configurators、routers三个节点信息。代码最终会执行到ZookeeperRegistry.doSubscribe(),这部分内容在前面讲解注册中心的文章已经讲解过。方法最后面会执行notify(url, listener, urls)方法:
因为订阅了三个节点目录,所以urls有三个元素URL值,并且只有节点是providers时URL将以dubbo协议开头,否则以empty开头。
notify()方法的作用主要有:
- 本地数据缓存:执行saveProperties(url),将节点信息缓存到本地的Properties对象,同时存储一份数据到本地磁盘目录。
- 更新本地提供者服务Invoker信息:执行listener.notify(categoryList),更新本地目录信息,内容通过执行RegistryDirectory.refreshInvoker(List invokerUrls)更新服务提供者Invoker信息。
那么这里的Invoker是如何创建的呢?在refreshInvoker()方法里面执行Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);
创建Invoker,Invoker的创建交给DubboProtocol:
@Override
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker =
new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
@6:根据Directory,利用集群策略返回集群Invoker,默认集群策略为默认为failover。
@7:更新ProviderConsumerRegTable消费者注册表信息consumerInvokers,consumerInvokers存储了消费者服务URL和对应的Invoker信息。
处理多注册中心情况
如果是多注册中心,需要将多个Invoker转换成一个Invoker,即需要执行invoker = cluster.join(new StaticDirectory(u, invokers));
首先通过最后一个注册中心地址和invokers实例化StaticDirectory,接着调用FailoverCluster.join(Directory directory)方法,返回FailoverClusterInvoker实例。
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
获取代理对象
获取到Invoker对象之后,执行proxyFactory.getProxy(invoker)
通过代理工厂ProxyFactory,获取到代理对象。
默认使用的代理工厂是JavassistProxyFactory,通过JavassistProxyFactory将Invoker对象转换成代理对象InvokerInvocationHandler。
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler) {
this.invoker = handler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
}