周末闲来无事,作为程序员只能时不时回味一下之前看过的知识点了,不然容易忘记。记录一下分析dubbo消费端初始化的源码。
我们都知道dubbo是一种rpc框架,配置完善后只需要从spring容器里面取出服务调用的代理对象就可以远程访问服务,这边文章就是分析这个代理对象的初始化过程的。
首先我使用的是基于dubbo2.6.1版本,注册中心是zookeeper。
之前写过一篇服务提供端初始化的源码分析,类似地,消费端的源码入口也是从配置加载开始,dubbo依赖spring提供的扩展方式解析dubbo自定义标签,将配置的属性装配为bean,然后根据配置bean进行一系列初始化。消费端相关配置是ReferenceBean对应着,ReferenceBean初值化完成后因为实现InitializingBean接口所以自动执行afterPropertiesSet,接下来主要初始化调用链是afterPropertiesSet->getObject->get->init->createProxy
其中createProxy返回结果就是远程访问的代理对象。接下来从createProxy开始分析.
createProxy处理之加载注册中心配置信息
//加载注册中心配置,获得的url是仅有注册中心信息,假设是zookeeper注册,格式是zookeeper://xxxxxxxx
List<URL> us = loadRegistries(false);
if (us != null && us.size() > 0) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
//map中包含配置的reference Service的信息,加入到url中
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
这里loadRegistries获取到的注册中心,urls.add添加服务信息,可以在后续操作连接到zookeeper获取可用服务的注册信息。
createProxy处理之获取rpc访问代理对象
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
//refprotocol.refer返回一个rpc访问代理对象
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url;
}
}
if (registryURL != null) {
// 设置使用AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
//无注册中心配置
invoker = cluster.join(new StaticDirectory(invokers));
}
(1)refprotocol.refer(interfaceClass, url)
之前协议一篇dubbospi机制的文章,基于dubbo的spi,分析一下,知道这里调用方法顺序是ProtocolFilterWrapper.refer->ProtocolListenerWrapper.refer->RegistryProtocol.refer,返回一个rpc访问对象,下面是RegistryProtocol.refer代码
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//registryFactory是适配器,url协议是zookeeper
Registry registry = registryFactory.getRegistry(url);
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 );
}
}
//cluster是适配器
return doRefer(cluster, registry, type, url);
}
registryFactory.getRegistry根据url协议获得zookeeper注册中心实例,然后执行doRefer,返回一个rpc访问对象,下面是代码
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//集群目录服务,持有可用服务列表invoker
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
//消费者url,用于注册到zookeeper注册中心comsumer的值,comsumer://xxxxx
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
//注册消费者到zookeeper
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
//订阅监听节点改变,同时刷新directory服务
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
//->MockClusterWrapper.join->FailoverCluster.join
//返回MockClusterInvoker,持有directory
return cluster.join(directory);
}
首先新建一个RegistryDirectory,先简单来说就是这个实例是持有这个服务所有可用服务rpc访问对象的实例,是核心对象。
然后就是构造一个消费者url注册到zookeeper注册中心的comsumer节点上面,表示它是服务的消费者。
再然后就是我们的重点, directory.subscribe方法,这里的作用就是做监听并且刷新directory里面所有可用服务rpc访问对象的列表,这里添加监听分别在
PROVIDERS_CATEGORY,CONFIGURATORS_CATEGORY,ROUTERS_CATEGORY三个节点,也就是[configurators, routers, providers]。下面是代码 directory.subscribe
public void subscribe(URL url) {
setConsumerUrl(url);
registry.subscribe(url, this);
}
directory持有的registry是zookeeper,下面进入代码是zookeeperRegistry父类FailbackRegistry方法, 在registry.subscribe(url, this)中,调用 doSubscribe(url, listener)这个方法由子类zookeeperRegistry实现,回到ZookeeperRegistry中,下面看ZookeeperRegistry.doSubscribe关键代码
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
//......
} else {
List<URL> urls = new ArrayList<URL>();
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List<String> currentChilds) {
//接收到事件通知,刷新directory的可用服务列表,这里的listener跟踪下来知道是Redirectory实例
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
zkClient.create(path, false);
//path是dubbo/xxxxservice格式,children是获得的可用服务的注册url信息
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//刷新directory可用服务列表
notify(url, listener, urls);
}
上面的代码第一件事就是给我们的[configurators, routers, providers]三个节点都加上了事件监听,在加监听的同时获取了节点下面的可用服务的注册值,用来刷新我们listener也就是Redirectory的可用服务列表。最终进入ZookeeperRegistry.notify方法,然后调用ZookeeperRegistry.doNotify方法,然后调用ZookeeperRegistry父类AbstractRegistry的notify方法,代码如下
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.size() == 0)
&& ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
//对url参数进行封装
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
saveProperties(url);
//调用Redirectory的notify刷新其服务列表
listener.notify(categoryList);
}
}
回到了Redirectory的notify方法,关键处是 refreshInvoker(invokerUrls),invokerUrls就是我们的url参数,进入Redirectory的refreshInvoker(invokerUrls)关键代码如下
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 将URL列表转成Invoker列表
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
// state change
//如果计算错误,则不进行处理.
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0 ){
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :"+invokerUrls.size() + ", invoker.size :0. urls :"+invokerUrls.toString()));
return ;
}
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
//设置Redirectory里面的可用服务列表
this.urlInvokerMap = newUrlInvokerMap;
上面的代码首先根据url创建rpc访问对象,并使用一个map来维护,然后刷新Redirectory里面持有的可用服务rpc访问对象map。
进入toInvokers(invokerUrls)看是如何创建一个rpc访问对象。
if (enabled) {
//url格式dubbo,protocol是适配器
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
}
上面是toInvokers方法的核心代码,protocol是适配器,所以先执行包装类的refer,最终执行是DubboProtocol的refer方法,代码如下
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
所以,最终在Redirectory持有的invoker格式是:【InvokerDelegete【DubboInvoker】】,而在DubboInvoker里面的doInvoke方法封装了rpc请求的处理。directory.subscribe这一句话就干了这么多事。我们回到先前的doRefer里面,轮到执行cluster.join(directory),这里面cluster是适配器,根据spi分析,方法调用链是MockClusterWrapper.join->FailoverCluster.join
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
那么最终dorefer返回对象是类似这种结构:
【MockClusterInvoker【FailoverClusterInvoker【Redirectory【InvokerDelegete【DubboInvoker】】】】】
回到ReferenceConfig的createProxy方法,接下来执行以下代码invokers,就是上面返回的代理对象。
if (registryURL != null) { // 配置了注册中心协议的URL
// AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // 不是 注册中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
使用注册中心,因此执行AvailableCluster,MockClusterWrapper.join->AvailableCluster.join
AvailableCluster.join代码如下
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new AbstractClusterInvoker<T>(directory) {
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
};
}
获得的invoker 结构变成【MockClusterInvoker【AbstractClusterInvoker【MockClusterInvoker【FailoverClusterInvoker【Redirectory【InvokerDelegete【DubboInvoker】】】】】】】
最终执行
// 创建服务代理
return (T) proxyFactory.getProxy(invoker);
根据api分析,getProxy代用链为
StubProxyFactoryWrapper.getProxy->JavassistProxyFactory.getProxy,最后执行到下面这段代码
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
Proxy.getProxy动态生成了一个类,断点查看这里的内容如下:
可以见到,类名是proxy0,方法有个sayhi是我们接口中的方法
public java.lang.String sayhi(java.lang.String arg0) {
Object[] args = new Object[1];
args[0] = ($w) $1;
Object ret = handler.invoke(this, methods[0], args);
return (java.lang.String) ret;
}
上面代码中的handler就是new InvokerInvocationHandler(invoker)传入的,结构如下
public class InvokerInvocationHandler implements InvocationHandler {
private final Invoker<?> invoker;
public InvokerInvocationHandler(Invoker<?> handler){
this.invoker = handler;
}
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();
}
}
所以InvocationHandler.invoke也算是调用远程方法的入口了。
最后查看一下依赖注入的HelloService是不是proxy0的实例。明显是的