假设我们有以下消费者配置
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用redis注册中心暴露发现服务地址 -->
<dubbo:registry address="redis://localhost:6379" />
<!-- Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭check,否则服务临时不可用时,会抛出异常,拿到null引用,如果check=false,总是会返回引用,当服务恢复时,能自动连上 -->
<!-- 生成远程服务代理,可以和本地bean一样使用demoService,check="false"关闭某个服务的启动检查,在没有提供者是会进行报错 -->
<!-- init="true"饥饿初始化 -->
<dubbo:reference id="service" interface="com.dubbo.service.UserService" />
<!-- 关闭全部服务的启动检查,没有提供者是报错 -->
<!-- <dubbo:consumer check="false"></dubbo:consumer> -->
<!-- 延迟初始化 -->
<!--<bean id="userAction" class="com.dubbo.action.UserAction" lazy-init="true">
<property name="service" ref="service"></property>
</bean>-->
</beans>
reference标签被解析成ReferenceBean
它实现了spring的FactoryBean,ApplicationContextAware,InitializingBean,DisposableBean接口
ApplicationContextAware与DisposableBean的实现与提供者启动是一样的逻辑,这里不过多的说明,我们现在主要关注InitializingBean与FactoryBean接口
这个FactoryBean在提供者端是没有实现的接口,那个为什么消费端要有呢?其实猜都猜得到这个,实现这个接口是为了生成当前配置接口的代理对象,实现调用远程接口犹如调用本地接口一样。
下面,我们来学习InitializingBean的实现
public void com.alibaba.dubbo.config.spring.ReferenceBean#afterPropertiesSet() throws Exception {
//检查是否有缺省配置ConsumerConfig
if (getConsumer() == null) {
//没有就到spring容器中找
Map<String, ConsumerConfig> consumerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class, false, false);
//找到没?
if (consumerConfigMap != null && consumerConfigMap.size() > 0) {
//找到了
ConsumerConfig consumerConfig = null;
for (ConsumerConfig config : consumerConfigMap.values()) {
//是默认的配置吗?
if (config.isDefault() == null || config.isDefault().booleanValue()) {
//存在多个默认配置,这哪能忍啊,当前ReferenceBean自己不引用一个默认的ConsumerConfig就算了,还认这么多人叫爸爸,肯定要报错
if (consumerConfig != null) {
throw new IllegalStateException("Duplicate consumer configs: " + consumerConfig + " and " + config);
}
consumerConfig = config;
}
}
//记录下来
if (consumerConfig != null) {
setConsumer(consumerConfig);
}
}
}
//有应用信息配置吗?
if (getApplication() == null
&& (getConsumer() == null || getConsumer().getApplication() == null)) {
//ReferenceBean自己没有,引用的默认配置ConsumerConfig也没有,那么只好到容器中找找看了
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
//容器中有吗?
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
//有的
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
//出现了多个默认配置吗?
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
//出现了
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
//把当前applicationConfig记为自己的默认的应用信息配置
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
//存在模块配置吗?
if (getModule() == null
&& (getConsumer() == null || getConsumer().getModule() == null)) {
//没有,那从容器中找吧
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
//找到了吗?
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
//找到了
for (ModuleConfig config : moduleConfigMap.values()) {
//不能出现多个默认的,要不然就报错
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
//它们,有配置配置了注册中心吗?
if ((getRegistries() == null || getRegistries().isEmpty())
&& (getConsumer() == null || getConsumer().getRegistries() == null || getConsumer().getRegistries().isEmpty())
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
//额,一个都没有,那到容器中找吧
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
//有没有找到注册配置
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
//注册中可以有多个默认的
if (config.isDefault() == null || config.isDefault().booleanValue()) {
registryConfigs.add(config);
}
}
if (registryConfigs != null && !registryConfigs.isEmpty()) {
super.setRegistries(registryConfigs);
}
}
}
//有注册中心吗?
if (getMonitor() == null
&& (getConsumer() == null || getConsumer().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
//那好吧,我去给你找个中心配置
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
//找到吗?
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (monitorConfig != null) {
//找到了,还不止一个
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
//初始化了吗?
Boolean b = isInit();
if (b == null && getConsumer() != null) {
//如果当前引用配置没有进行初始化,那么看下ConsumerConfig有没有进行初始化
b = getConsumer().isInit();
}
if (b != null && b.booleanValue()) {
getObject();
}
}
引用配置的初始化,初始化其实就是生成接口代理对象,这也是实现FactoryBean接口要干的事情,下面我继续看到getObject方法
public Object com.alibaba.dubbo.config.spring.ReferenceBean#getObject() throws Exception {
return get();
}
|
V
public synchronized T com.alibaba.dubbo.config.ReferenceConfig#get() {
//如果bean已经被销毁就不允许继续初始化了
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
|
V
private void com.alibaba.dubbo.config.ReferenceConfig#init() {
if (initialized) {
return;
}
initialized = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// get consumer's global configuration
//检查是否有默认的配置,这个默认的配置就是ConsumerConfig,如果确实没有,那么也会创建一个consumerConfig,然后尝试从系统变量,系统属性的获取值
checkDefault();
//从系统属性,变量,获取通过系统属性指定的目标配置文件获取属性值,追加参数,值得注意的是,如果当前属性有值了,系统属性配置的值具有更高的优先级
appendProperties(this);
//是否使用泛化接口
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
//检查是否需要使用泛化调用,一般用于消费端没有对应接口依赖包时使用
//可以指定三种泛化时对参数进行的序列化方式:
//Constants.GENERIC_SERIALIZATION_DEFAULT:这种方法在我们分析解析请求体数据的时候可以看到,dubbo使用PojoUtil助手类进行通用参数类型转换
//Constants.GENERIC_SERIALIZATION_NATIVE_JAVA:这种方式就是使用的JDK的ObjectInputStream
//Constants.GENERIC_SERIALIZATION_BEAN:这种使用的是dubbo的JavaBeanDescriptor方式,这个没有仔细研究
if (ProtocolUtils.isGeneric(getGeneric())) {
//接口类型修改为GenericService,但是interfaceName不会发生改动,它还要通过它找到远在服务端的接口
//这个GenericService接口的方法只有一个,名字叫$invoke,有三个参数,第一个参数为目标接口的方法名,第二个参数
//是目标方法的参数类型,第三个参数就是传给目标方法的参数值
interfaceClass = GenericService.class;
} else {
try {
//解析接口class对象
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查配置的细粒度方法配置是否是当前接口的方法
//配置methods是为了做到方法级别的控制,比如重试次数,超时时间都可以不同,就像有些方法处理逻辑比较复杂
//这个方法可能就需要更多的时间去等待
checkInterfaceAndMethods(interfaceClass, methods);
}
//以接口名为key获取系统属性,属性值为消费者与提供者的点对点直连
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
//没有,那么从系统属性的dubbo.resolve.file获取properties配置文件
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
//还是没有,那么从用户目录下寻找dubbo-resolve.properties
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
resolveFile = userResolveFile.getAbsolutePath();
}
}
//读取配置文件
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
//点对点消费者与提供者互连的url
url = resolve;
if (logger.isWarnEnabled()) {
if (resolveFile != null) {
logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
} else {
logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
}
}
}
//。。。。。。省略部分配置是否为空的判断
//依然是检查应用信息是否配置,没有配置就创建一个空的,然后从系统属性,变量或者指定的配置文件中获取
checkApplication();、
//检查是否存在存根服务,所谓存根服务就是每次发起服务调用的时候就会调用这个存个服务,它实现了当前消费接口,一般用于
//检查是否有缓存,如果没有再调用代理的接口实现
//如果没有指定存根实现服务的类名,那么默认以接口名+"Local",这个类名,我们在配置消费者时配置local属性,这个local属于被废弃的属性
//新的应该使用stub,对应的默认的类名就是接口名+"Stub"
checkStub(interfaceClass);
//检查mock服务,里面的逻辑我们在第二小节的时候就已经分析过了,此处不再赘述
//mock的默认类名就是接口名+"Mock",在远程接口调用失败的时候,会调用这个服务。
checkMock(interfaceClass);
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
//添额外参数,表示当前配置属于消费端
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
//dubbo协议版本
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
//创建时间戳
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
//设置当前消费者运行的jvm进程号
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
//如果不是泛化的
if (!isGeneric()) {
//获取调整版本,首先从jar的META-INF中找,如果没有,再到当前接口所在的jar名字上找,如果还是没有就是用配置的默认版本
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
//获取这个接口的所有public方法名,这个Wrapper的具体操作逻辑我们在第二节的提供者配置分析过,通过javassist进行生成的一个可以获取属性,方法
//设置属性,调用方法的类,具体的生成方式此处不再赘述
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
//使用逗号分割,记录当前接口存在的所有public方法
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
//interface -> 接口名
map.put(Constants.INTERFACE_KEY, interfaceName);
//添加应用信息
appendParameters(map, application);
//添加模块信息
appendParameters(map, module);
//添加默认的消费者配置,默认的配置的key都会有一个default前缀
appendParameters(map, consumer, Constants.DEFAULT_KEY);
//添加自身的配置
appendParameters(map, this);
//group名/接口名/我们配置的版本
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
//添加方法配置参数,以方法名作为前缀
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
//将老的配置方式更正为新的配置方式
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
//添加属性,以group名/接口名/我们配置的版本.方法名最前缀
appendAttributes(attributes, method, prefix + "." + method.getName());
//检查和转换方法配置的onreturn,onthrow,oninvoke对应的方法是否存在,并把String类型的方法名修改为具体的Method对象
checkAndConvertImplicitConfig(method, map, attributes);
}
}
//获取用于注册的host
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
//没有配置就获取本地ip地址
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
//无效的地址
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
//添加注册ip地址
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//attributes are stored by system context.
//将属性值存储起来
StaticContext.getSystemContext().putAll(attributes);
//创建代理对象
ref = createProxy(map);
//将当前消费者进行包装,记录在com.alibaba.dubbo.config.model.ApplicationModel#consumedServices Map集合中
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
createProxy
private T com.alibaba.dubbo.config.ReferenceConfig#createProxy(Map<String, String> map) {
//创建一个临时的Url
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
//是否直接本地连接
if (isInjvm() == null) {
//如果指定了消费者与提供者直连的url,那么取消本地应用
if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
isJvmRefer = false;
//检测是否需要直接本地连接
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
if (isJvmRefer) {
//构建本地连接协议地址 injvm://127.0.0.1:0/接口名?map参数(变成key1=value1&key2=value2字符串)
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
//启动socket连接提供者,返回一个invoker
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
//用户指定的url地址,可能是消费者与提供者直连地址,也有可能是注册中心地址
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
//分号分割
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
//设置路径为接口名
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
//是否是registry协议
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
//refer -> map参数
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
//合并当前配置的map参数值到用户提供的url的参数中
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
//获取注册中心地址,并处理用户配置的注册中心地址,url的协议被修改为registy,真正的协议被设置为url的参数
//加入注册中心为redis,那么这个url就会有一个register-》redis 参数,加载注册中心地址的逻辑在分析提供者的时候已经讲过
//此处不再追溯了
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
//加载注册中心地址
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
//添加参数 monitor -》 monitorUrl.toFullString()
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
//添加refer-》map参数(变成那种key1=value1&key2=value2的形式)
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
//没有指定注册中心,抛错
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
//如果只有一个,直接连接
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
//有多个注册中心/直连提供者地址,创建多个连接
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
//是否是注册url,判断其协议是否为registry
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
//记录最后一个注册中心地址
registryURL = url; // use last registry url
}
}
//如果不为空,那么当前url就是注册url
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
//添加标记,表示这个url是一个注册中心地址,并且在注册中心集群中是个可用的地址
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
//进行路由匹配,负载均衡获取其中一个invoker
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
//不是注册中心地址,那么就是直连地址
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
//启动时检查提供者是否存在,true报错,false忽略
Boolean c = check;
if (c == null && consumer != null) {
//检查默认配置是否配置了这个值
c = consumer.isCheck();
}
//如果没有配置,那么默认为true
if (c == null) {
c = true; // default true
}
//检查提供者是否可用,不可用,抛出错误
if (c && !invoker.isAvailable()) {
// make it possible for consumer to retry later if provider is temporarily unavailable
initialized = false;
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
// create service proxy
//创建代理,默认使用JavassistProxyFactory创建代理,它的代理逻辑我们在前面分析接口的调用的时候讲过了
//此处不再赘述
return (T) proxyFactory.getProxy(invoker);
}
上面有这么一段代码
invoker = refprotocol.refer(interfaceClass, url);
这个refprotocol是通过SPI加载的Protocol实现,它由javassit生成的一段代码,代码如下
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
//对于没有没有被@Adaptive注解修饰的方法直接抛出不支持异常
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
//。。。。。。省略获取默认端口方法
//。。。。。。省略服务export方法
//查找服务,arg0:接口class对象,arg1:注册中心地址或者直连服务提供者的地址
public com.alibaba.dubbo.rpc.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
//对于注册中心,这里的协议是registry
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
//既然是注册中心url,那么这里获取的协议就是RegistryProtocol,如果是直连提供者的地址并且提供协议为dubbo,那么获取就是DubboProtocol
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
RegistryProtocol#refer
public <T> Invoker<T> com.alibaba.dubbo.registry.integration.RegistryProtocol#refer(Class<T> type, URL url) throws RpcException {
//恢复原来配置的协议,假设我们原来配置的是一个redis://xxx地址,那么协议就会变回redis
//redis://localhost:6379/com.alibaba.dubbo.registry.RegistryService?application=consumer-of-helloworld-app&dubbo=2.0.2&pid=28380&refer=application%3Dco
//nsumer-of-helloworld-app%26dubbo%3D2.0.2%26interface%3Dcom.dubbo.service.UserService%26methods%3DsayHello%26pid%3D28380%26register.ip%3D192.168.56.1%26
//side%3Dconsumer%26timestamp%3D1568454846115×tamp=1568455009235
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//从注册工厂中获取对应协议的注册器,假设我们使用的是redis,那么这里返回的就是RedisRegistry
//RedisRegistry的构造器逻辑我们在第9小节服务的注册中分析过,此处不再额外分析
Registry registry = registryFactory.getRegistry(url);
//如果接口类型是注册服务,RedisRegistry就是注册服务的实现者
if (RegistryService.class.equals(type)) {
//直接使用把当前构建出来的RedisRegistry实例包装成invoker返回
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
//将refer参数由key value字符串解析会map
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)) {
//getMergeableCluster()获取的是一个MergeableCluster,如果参数中指定了merger参数
//以点开头,比如 .merge,表示接口返回值存在一个merge方法,dubbo将所有符合条件的invoker返回值进行merge
//如果不是以点开头的,那么判断返回值是否是集合类型,如果是的话,dubbo将创建自己实现了Merger的集合去merge
return doRefer(getMergeableCluster(), registry, type, url);
}
}
//普通cluster,虽然是通过javassist生成的实例,但是默认最终使用的就是FailoverCluster
return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> com.alibaba.dubbo.registry.integration.RegistryProtocol#doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//构建RegistryDirectory,这个从名字上叫做注册目录,它的接口有一个list方法,返回值是一个invoker列表
//简单理解为是一个维护invoker列表的目录吧
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
//设置注册器
directory.setRegistry(registry);
//设置协议对象
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
//构建订阅url
//consumer://host:port/接口名?参数对
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)) {
//给url添加类目信息参数:category -》 consumers, check -》 false
URL registeredConsumerUrl = getRegisteredConsumerUrl(subscribeUrl, url);
//注册这个地址registeredConsumerUrl,注册逻辑和第九节我们分析提供者地址的注册是一样的,不过多说明
//只不过它所注册的类目是consumers
registry.register(registeredConsumerUrl);
//记录这个被注册的消费者url到RegistryDirectory中
directory.setRegisteredConsumerUrl(registeredConsumerUrl);
}
//订阅providers,configurators,routers,当这些redis通道发生变化时会收到提醒
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
//如果没有接口group,那么这个cluster最终调用的就是FailoverCluster),
//它的join方法返回FailoverClusterInvoker(失败转移,当某个提供者调不通的时候会重新从列表中筛选一个)
Invoker invoker = cluster.join(directory);
//用一个com.alibaba.dubbo.registry.support.ProviderConsumerRegTable#consumerInvokers map集合记录
//消费者信息
//(*1*)
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
//(*1*)
public static void registerConsumer(Invoker invoker, URL registryUrl, URL consumerUrl, RegistryDirectory registryDirectory) {
//(*2*)
//将消费信息进行打包
ConsumerInvokerWrapper wrapperInvoker = new ConsumerInvokerWrapper(invoker, registryUrl, consumerUrl, registryDirectory);
//group名/接口名:version
String serviceUniqueName = consumerUrl.getServiceKey();
//从缓存中获取
Set<ConsumerInvokerWrapper> invokers = consumerInvokers.get(serviceUniqueName);
if (invokers == null) {
consumerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ConsumerInvokerWrapper>());
invokers = consumerInvokers.get(serviceUniqueName);
}
invokers.add(wrapperInvoker);
}
//(*2*)
//就是单纯的用一个bean来保存数据而已
public ConsumerInvokerWrapper(Invoker<T> invoker, URL registryUrl, URL consumerUrl, RegistryDirectory registryDirectory) {
this.invoker = invoker;
this.originUrl = URL.valueOf(invoker.getUrl().toFullString());
this.registryUrl = URL.valueOf(registryUrl.toFullString());
this.consumerUrl = consumerUrl;
this.registryDirectory = registryDirectory;
}
订阅redis通道
//url:consumerUrl(协议为consumer://本地机器ip/当前消费者接口名?当前消费者配置参数),listene:RegistryDirectory
public void com.alibaba.dubbo.registry.support.FailbackRegistry#subscribe(URL url, NotifyListener listener) {
(*1*)
//父类方法对url与listener做了空判断,然后将url与listerner以url -> List<listener>的形式注册到subscribed map集合中
super.subscribe(url, listener);
//移除以url为key的订阅失败的,取消订阅失败的,通知失败的监听器
removeFailedSubscribed(url, listener);
try {
// Sending a subscription request to the server side
//订阅
doSubscribe(url, listener);
} catch (Exception e) {
。。。。。。省略部分异常处理
// Record a failed registration request to a failed list, retry regularly
//记录订阅失败的url,在创建Redis注册器时构建一个调度器,每个一段时间会进行重试
addFailedSubscribed(url, listener);
}
}
//(*1*)
public void subscribe(URL url, NotifyListener listener) {
if (url == null) {
throw new IllegalArgumentException("subscribe url == null");
}
if (listener == null) {
throw new IllegalArgumentException("subscribe listener == null");
}
if (logger.isInfoEnabled()) {
logger.info("Subscribe: " + url);
}
Set<NotifyListener> listeners = subscribed.get(url);
if (listeners == null) {
subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
listeners = subscribed.get(url);
}
listeners.add(listener);
}
doSubscribe
//这个url是消费端url
public void doSubscribe(final URL url, final NotifyListener listener) {
// /dubbo/com.dubbo.service.UserService
String service = toServicePath(url);
//获取通知器,Notifier是一个继承了Thread线程类
Notifier notifier = notifiers.get(service);
if (notifier == null) {
//创建一个新的通知器,用于监听提供者的注册和注销,以便能动态更新提供者列表
Notifier newNotifier = new Notifier(service);
notifiers.putIfAbsent(service, newNotifier);
notifier = notifiers.get(service);
if (notifier == newNotifier) {
//启动通知器
notifier.start();
}
}
boolean success = false;
RpcException exception = null;
//循环每台redis服务连接池
for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
JedisPool jedisPool = entry.getValue();
try {
//获取redis连接
Jedis jedis = jedisPool.getResource();
try {
//如果key是*结尾的
if (service.endsWith(Constants.ANY_VALUE)) {
admin = true;
//获取所有匹配的值
Set<String> keys = jedis.keys(service);
if (keys != null && !keys.isEmpty()) {
Map<String, Set<String>> serviceKeys = new HashMap<String, Set<String>>();
for (String key : keys) {
//获取接口名
String serviceKey = toServicePath(key);
//以接口名进行分组
Set<String> sk = serviceKeys.get(serviceKey);
if (sk == null) {
sk = new HashSet<String>();
serviceKeys.put(serviceKey, sk);
}
sk.add(key);
}
//循环以接口分组的值
for (Set<String> sk : serviceKeys.values()) {
doNotify(jedis, sk, url, Arrays.asList(listener));
}
}
} else {
doNotify(jedis, jedis.keys(service + Constants.PATH_SEPARATOR + Constants.ANY_VALUE), url, Arrays.asList(listener));
}
success = true;
break; // Just read one server's data
} finally {
jedis.close();
}
} catch (Throwable t) { // Try the next server
exception = new RpcException("Failed to subscribe service from redis registry. registry: " + entry.getKey() + ", service: " + url + ", cause: " + t.getMessage(), t);
}
}
if (exception != null) {
if (success) {
logger.warn(exception.getMessage(), exception);
} else {
throw exception;
}
}
}
doNotify
private void com.alibaba.dubbo.registry.redis.RedisRegistry#doNotify(Jedis jedis, Collection<String> keys, URL url, Collection<NotifyListener> listeners) {
if (keys == null || keys.isEmpty()
|| listeners == null || listeners.isEmpty()) {
return;
}
long now = System.currentTimeMillis();
List<URL> result = new ArrayList<URL>();
//获取类目,providers,configurators,routers
List<String> categories = Arrays.asList(url.getParameter(Constants.CATEGORY_KEY, new String[0]));
//获取消费者接口名
String consumerService = url.getServiceInterface();
for (String key : keys) {
if (!Constants.ANY_VALUE.equals(consumerService)) {
//获取当前key的提供者接口名,不出意外的话,和我们消费者的接口名是一样的
//因为我们调用jedis.keys的时候使用的通常就是/dubbo/接口名/*去匹配的
String prvoiderService = toServiceName(key);
if (!prvoiderService.equals(consumerService)) {
continue;
}
}
//获取类目,如果我们启动了服务提供者,那么redis中就会存在名为prvoiders的类目
//并且在前面dubbo注册了消费者url,所有又会有consumers类目
String category = toCategoryName(key);
//如果当前key不是我们所需要订阅的类目,那么跳过
if (!categories.contains(Constants.ANY_VALUE) && !categories.contains(category)) {
continue;
}
List<URL> urls = new ArrayList<URL>();
//获取这个key的所有值,如果是提供者,那么这个map就是提供者URL-》过期时间戳
Map<String, String> values = jedis.hgetAll(key);
if (values != null && values.size() > 0) {
for (Map.Entry<String, String> entry : values.entrySet()) {
//url
URL u = URL.valueOf(entry.getKey());
//非动态的,或者这个提供者地址没有过期,进行匹配
//非动态服务提供者不会进行更新,使用都需要人工启动,提供者挂掉不会取消注册
//动态服务提供者会动态刷新,当提供者挂掉后,会自动移除注册
if (!u.getParameter(Constants.DYNAMIC_KEY, true)
|| Long.parseLong(entry.getValue()) >= now) {
if (UrlUtils.isMatch(url, u)) {
urls.add(u);
}
}
}
}
//如果没有找到任何的提供者,路由或者配置器,添加一个协议为empty
//地址为0.0.0.0,当前key的接口名,类别为当前类目名的空url
if (urls.isEmpty()) {
urls.add(url.setProtocol(Constants.EMPTY_PROTOCOL)
.setAddress(Constants.ANYHOST_VALUE)
.setPath(toServiceName(key))
.addParameter(Constants.CATEGORY_KEY, category));
}
result.addAll(urls);
if (logger.isInfoEnabled()) {
logger.info("redis notify: " + key + " = " + urls);
}
}
//为空,直接返回
if (result == null || result.isEmpty()) {
return;
}
//通知
for (NotifyListener listener : listeners) {
notify(url, listener, result);
}
}
上面一段代码主要是获取类别为providers,configurators,routers下有效的url
protected void com.alibaba.dubbo.registry.support.AbstractRegistry#notify(URL url, NotifyListener listener, List<URL> urls) {
//当前消费者url
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
//通知观察者
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((urls == null || urls.isEmpty())
&& !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) {
//是否是与消费者url相匹配的url,匹配条件大致是接口相同,组,版本相同,但是类目不能相同等等
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);
}
categoryList.add(u);
}
}
//没有直接返回
if (result.size() == 0) {
return;
}
//消费者url -》 (类目 -》 redis类目下对应的复合条件的url)
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();
//获取该类目下未过期且消费者url匹配的url
List<URL> categoryList = entry.getValue();
//记录到缓存中
categoryNotified.put(category, categoryList);
//以key为消费者url,value为url空格分隔的形式存到 用户目录\.dubbo\dubbo-registry-consumer-of-helloworld-app-localhost:6379.cache
//properties文件中
saveProperties(url);
//调用监听器
listener.notify(categoryList);
}
}
上面一段代码主要在给提供者url/配置器url/路由url进行分类并把这些url进行写入硬盘备份,最后调用了com.alibaba.dubbo.registry.integration.RegistryDirectory#notify方法,这个方法是用来做什么的呢?这个方法的逻辑在下一节分析。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?