在dubbo中,dubbo通过DubboNamespaceHandler
类对dubbo配置文件中的各个标签内容进行初始化操作。
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
其中"service"对应的ServiceBean.class中定义了Dubbo中服务是如何export的,即调用export()方法。
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
在dubbo中支持两种导出方式:延迟导出和立即导出。延迟导出入口对应的为ServiceBean中的afterPropertiesSet方法,立即导出的入口对应了ServiceBean中的onApplicationEvent方法。
onApplicationEvent
我们主要关注立即导出onApplicationEvent方法。如下所示:
/**
* export
* @param event
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 已经暴露 不暴露,初始二者均为true
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
/**
* @since 2.6.5
*/
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
publishExportEvent()
中内容很简单,我们主要关注super.export()
中的内容,super.export()
即调用了org.apache.dubbo.config.ServiceConfig#export()
方法。
public synchronized void export() {
// 一系列校验
checkAndUpdateSubConfigs();
// 是否暴露,若不暴露,则直接返回
if (!shouldExport()) {
return;
}
// 判断是否需要延迟加载
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
在checkAndUpdateSubConfigs()
方法中主要做了一系列的检验工作,校验各种对象是否为空,若为空则新创建或者抛出异常,我们不做深究,关注主流程。在export()
方法中判断是否export(),如果为false,则直接return。如果不需要延迟加载,则直接doExport()
,下面主要来看这个方法。
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
// 如果配置export=false,禁止服务导出,则返回。<dubbo:provider export="false" />
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
我们可以看到在doExport()方法中,最终调入了doExportUrls()方法。
/**
* 进行导出
* dubbo 支持多协议的导出
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
// Dubbo是基于URL驱动的。获取的List<URL>是zookeeper://localhost:2181类型的URL。我们可以将服务发布到多个注册中心
// 所以该registryURLs为一个list
List<URL> registryURLs = loadRegistries(true);
// 遍历protocols,dubbo支持多协议的暴露,如rmi、dubbo
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
在loadRegistries中,主要是获得注册中心的URL,因为dubbo允许我们将服务发布到多个注册中心中,所以loadRegistries的返回值是一个List。
/**
* Load the registry and conversion it to {@link URL}, the priority order is: system property > dubbo registry config
* 加载注册中心的URL
* <p>
* loadRegistries 方法主要包含如下的逻辑:
* <p>
* 检测是否存在注册中心配置类,不存在则抛出异常
* 构建参数映射集合,也就是 map
* 构建注册中心链接列表
* 遍历链接列表,并根据条件决定是否将其添加到 registryList 中
*
* @param provider whether it is the provider side
* @return
*/
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
// 当注册中心配置不为空,遍历得到每个注册中心的地址。
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
// 如果address为空,则将其设置为0.0.0.0
address = ANYHOST_VALUE;
}
// 注册中心地址不为N/A时,添加进registryList中
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// 添加 ApplicationConfig RegistryConfig字段信息添加到map中。
appendParameters(map, application);
appendParameters(map, config);
// 添加path、protocol、pid、release、timestap等信息到map中
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
// 解析得到URL列表,address中可能包含多个注册中心的ip,因此解析得到的是一个URL列表
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
// 在url中设置协议头为registry,并添加参数。
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
// 通过条件判断,决定是否添加url到registryList中,条件判断:
// (服务提供者 && registry == true 或 null) ||
// (非活动提供者 && subscribe = true 或 null)
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
在上述代码中已经做了详尽的注释,暂且不做过多的解释,我们还是去关注export()的主流程,进入doExportUrlsFor1Protocol()方法。
/**
* 方法中出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。
*
* Invoker在dubbo中是一个非常重要的模型,在服务提供端和服务引用段都会出现Invoker。
* - Invoker是实体域,是dubbo的核心模型,其他模型向它靠拢或转换成它,它代表了一个可执行体。可以向它发起Invoke调用,可能是一个本地的实现
* 也可能是一个远程的是实现,也可能是一个集群实现。
*
* @param protocolConfig
* @param registryURLs
*/
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 一系列的组装参数。构造URL,注意:这个URL不是java.net.URL,而是org.apache.dubbo.common.URL
// ======================================= 分割线 =======================================
// 下面这一部分将进入真正的dubbo服务的导出
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 加载ConfiguratorFactory,并生成Configurator实例,然后加载上面生成的url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// 从url中得到scope
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
// 如果scope为none时,什么都不做
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// 如果配置的不是远程导出(remote),则进行本地导出,调用exportLocal(url);
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
// 如果不是本地导出(local),则进行远程暴露.只有当scope配置为local的时候,才进行本地导出。
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
// 如果url中的protocol,不为injvm,则继续处理下一个注册中心url
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
// 加载监听器连接,如果监听器url不为null,则将监听器链接作为参数添加到url中
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
// 加载监控、URL中添加监控地址信息、日志
if (logger.isInfoEnabled()) {
if (url.getParameter(REGISTER_KEY, true)) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
} else {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
}
// For providers, this is used to enable custom proxy to generate invoker
// 获得url中的代理自动,才可以创建一个代理,进行invoke
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
// 为服务提供类生成Invoker,代理工厂创建一个代理,获得一个invoker。默认使用javassist方式
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// 将Invoker、serviceConfig传递进去,DelegateProviderMetaDataInvoker持有了他们
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 按照相应的protocol暴露wrapperInvoker对象。
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
// 当注册中心url的list为空时,默认生成一个代理类。
// 即不存在注册中心时,仅导出服务
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
this.urls.add(url);
}
在doExportUrlsFor1Protocol()
方法中我们省略了一些构造URL的无关代码,主要关心dubbo服务导出的过程。首先加载了ConfiguratorFactory扩展点,并生成Configurator实例,配置构造完成的URL。之后从url中取得参数:scope,又根据的scope的值决定了不同的导出方式,具体如下:
- scope = none,不导出服务
- scope != remote,则进行本地导出服务
- scope != local,则进行远程导出服务
若scope != remote,则进行本地导出服务,则直接调用exportLocal(url)方法;若scope != local,则进行远程导出服务,会根据URL的list加载监听器、日志等参数,最终生成Invoker,完成对protocol的export调用。在上述代码中,做了较为详细的注释解释,大家可以参考理解。
不论是local导出或者remote导出,在导出之前都会生成一个Invoker对象,由此可见Invoker的创建过程是一个很重要的过程。
Invoker的创建过程
在dubbo中,Invoker是一个非常重要的概念,在服务的提供端以及引用端都会出现Invoker,在dubbo官方文档中,Invoker被这样介绍:Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
在dubbo中,Invoker的默认实现为javassist方式。下面我们来看一下JavassistProxyFactory代码,代码如下:
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 为目标类创建Wrapper。
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// 创建匿名Invoker类对象,并实现doInvoker方法。
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 调用wrapper的invokeMethod方法,invokeMethod最终会调用目标方法。
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
我们省去其他不必要的代码,主要看getInvoker方法。getInvoker方法中,首先通过Wrapper为目标类创建Wrapper,然后创建匿名Inovker类对象,并实现doInvoke方法,在doInvoke方法中,会调用上面创建的Wrapper的invokeMehtod方法,invokeMethod方法最终会调用目标方法。到这Invoker的生成过程就结束了。
在获得invoke后,封装DelegateProviderMetaDataInvoker,它将持有invoke和serviceConfig对象。最后调用protocol.export(wrapperInvoker)进行服务的导出。
导出服务
首先我们先来看本地导出exportLocal(),由代码的注释可以看出,仅导出URL的协议头等于 injvm的URL。代码如下:
/**
* always export injvm
*/
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
// 创建Invoker,并导出服务,这里的protocol会在运行时调用InjvmProtocol的export方法
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
上面本地导出的逻辑较为简单,不做过多的解释。远程导出代码在上面代码中已经有过分析,不在赘述。之后会进入真正进入export的过程,期间会经过一个调用链过程。具体过程如下图UML所示:
虽然在export中还存在一些创建server的过程,但是不在服务导出的主流程内,在这里就不做分析了,如果大家有兴趣可以单独进行了解,下附一张export的导出过程流程图,供大家参考,如有不妥之处,请大家指出。