暴露服务时序:原图地址:http://dubbo.apache.org/zh-cn/docs/dev/design.html
解析service标签,会将service标签封装成ServiceBean:
ServiceBean怎么做:
public class ServiceBean<T> extends ServiceConfig<T>
implements InitializingBean, DisposableBean, ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, BeanNameAware {...}
1.ServiceBean实现了InitializingBean:
- InitializingBean是Spring里面的一个重要接口,当组件创建完对象之后,会调用InitializingBean;
- 在属性设置完之后,会调用afterPropertiesSet方法:
2.ServiceBean实现了ApplicationListener:
- 应用的监听器,Spring原理里面核心的一部分;
- ApplicationListener监听的事件是:ContextRefreshedEvent;
- 当IOC容器刷新完成/IOC容器里面所有对象创建完,会回调ApplicationListener里面的onApplicationEvent方法;
3.ServiceBean在容器创建完对象以后,调用方法afterPropertiesSet;
还会在IOC容器启动完成后,调用onApplicationEvent;
public class ServiceBean<T> extends ServiceConfig<T>
implements InitializingBean, DisposableBean, ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, BeanNameAware {
...
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
...
}
...
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception {
...
}
...
}
ServiceBean的afterPropertiesSet方法里面有如下内容:
public void afterPropertiesSet() throws Exception {
...
setProvider(providerConfig);
...
setApplication(applicationConfig);
...
setModule(moduleConfig);
...
super.setRegistries(registryConfigs);
...
setMonitor(monitorConfig);
...
super.setProtocols(protocolConfigs);
...
}
setProvider(providerConfig):是将标签里面配的provider信息保存起来,保存到当前的ServiceBean里面;setApplication(applicationConfig);setModule(moduleConfig);super.setRegistries(registryConfigs);setMonitor(monitorConfig);super.setProtocols(protocolConfigs);方法则类似;
afterPropertiesSet方法:相当于将以前已经配置的信息,都保存起来;
下一步回调了:onApplicationEvent;
ServiceBean的onApplicationEvent方法里面有如下内容:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " +
getInterface());
}
export();
}
}
当IOC容器:ContextRefreshedEvent event,刷新完成之后,调一个方法:
if (isDelay() && !isExported() && !isUnexported()) :
-
如果不是延迟暴露,而且还没暴露,而且还不是不暴露:既要暴露,还没暴露,而且还不是延迟暴露,就会调用export()方法;
-
export();方法用于暴露服务;
相关流程图如下:
export()如何暴露?
export()方法:为ServiceConfig类里面的export()方法:
public class ServiceConfig<T> extends AbstractServiceConfig {
...
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
...
public synchronized void export() {
...
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
...
protected synchronized void doExport() {
...
doExportUrls();
...
}
...
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
...
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
...
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
...
}
}
export()方法的关键一步:doExport(),也就是:执行暴露;
doExport()方法的关键一步:doExportUrls(),也就是:执行暴露url地址;
doExportUrls():
- 第一步:加载注册中心的信息,读取注册中心的地址;
- 第二步:暴露服务。如果要暴露在多个端口(for循环),可以配置多个<dubbo:protocol...>的标签。protocol:协议,可以使用dubbo协议等等,也可以使用其他协议去暴露;
- 调用doExportUrlsFor1Protocol(..., ...)方法;
doExportUrlsFor1Protocol(..., ...):
- 于此同时,观察注册中心,方法什么时候被注册到注册中心,是可以被看见的;
- 有一些重要的方法:proxyFactory(代理工厂),要获取一些invoker(执行器)。这个invoker实际上是将暴露的服务包装了一下:服务的实现、服务的地址等等,组合成了一个invoker;
- wrapperInvoker:对执行器再次进行包装;
- 包装完成后,会调用protocol.export(...)方法暴露invoker,一暴露就会生成exporter,exporter会被保存起来;
- protocol是通过这种方式得到的(详见代码):Protocol.class获取扩展的类加载器,得到它的适配。我们会得到protocol接口里面当前要用的(Protocol有多个实现);
- 由于使用Dubbo协议进行暴露,于是使用DubboProtocol;我们也会把它注册到注册中心,于是使用RegistryProtocol;
- DubboProtocol和RegistryProtocol都有一个方法export(),暴露是如何进行的?
- 会先来到RegistryProtocol的export()方法,先来到注册中心来暴露;
public class RegistryProtocol implements Protocol {
...
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
...
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
if (register) {
register(registryUrl, registedProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
...
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
...
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
...
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}
}
- RegistryProtocol的export()方法:第一步:originInvoker是原生的执行者,封装了服务的实现、接口和url地址。doLocalExport来做本地的暴露;第二步:ProviderConsumerRegTable(提供者和消费者的注册表),注册表里面注册提供者,注册就是把提供者、注册中心地址、提供者的url地址传进来;
- doLocalExport方法:第一步:做一个暴露器:exporter,拿到要暴露的执行者,调用protocol(DubboProtocol)进行暴露(使用Dubbo协议进行服务的暴露);
public class DubboProtocol extends AbstractProtocol {
...
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
...
openServer(url);
...
return exporter;
}
private void openServer(URL url) {
...
String key = url.getAddress();
...
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
private ExchangeServer createServer(URL url) {
...
ExchangeServer server;
try {
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
...
}
...
return server;
}
...
}
- 先拿到url地址,相当于我们要在注册中心里面调用的地址。把地址各种转化,包装成DubboExporter(暴露器);
- DubboProtocol会做一件事:openServer(打开服务器);
- openServer会先拿到地址(服务暴露的端口),ExchangeServer会调用createServer来创建一个信息交换的服务器(如果server为空);
- createServer方法会创建一个服务器(ExchangeServer),调用Exchangers.bind(..., ...)方法来绑定服务器和请求的处理器。这个bind其实是Transporters(传输器)来进行bind,Transporters进行的bind就会进入NettyTransporter的底层了(启动netty服务器等);
- 所以,当我们openServer,先是创建服务器时,其实就是启动netty服务器,监听某一端口(eg:20880);
RegistryProtocol的doLocalExport(...)方法的下面:
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl):是ProviderConsumerRegTable注册提供者;
public class ProviderConsumerRegTable {
public static ConcurrentHashMap<String, Set<ProviderInvokerWrapper>> providerInvokers = new ConcurrentHashMap<String, Set<ProviderInvokerWrapper>>();
public static ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>> consumerInvokers = new ConcurrentHashMap<String, Set<ConsumerInvokerWrapper>>();
public static void registerProvider(Invoker invoker, URL registryUrl, URL providerUrl) {
ProviderInvokerWrapper wrapperInvoker = new ProviderInvokerWrapper(invoker, registryUrl, providerUrl);
String serviceUniqueName = providerUrl.getServiceKey();
Set<ProviderInvokerWrapper> invokers = providerInvokers.get(serviceUniqueName);
if (invokers == null) {
providerInvokers.putIfAbsent(serviceUniqueName, new ConcurrentHashSet<ProviderInvokerWrapper>());
invokers = providerInvokers.get(serviceUniqueName);
}
invokers.add(wrapperInvoker);
}
...
}
- 这个注册其实是保存一些信息:providerInvokers(服务提供者的执行者),与此同时,有consumerInvokers。
- providerInvokers和consumerInvokers保存:url地址,和对应的服务提供者/消费者的执行器;
- 执行器里面有真正的服务的对象的实现等等,把这种管理关系(要暴露的服务和地址)维护在这里,至此,服务暴露;
服务暴露的两步:
- DubboProtocol会启动Netty的服务器,监听某一端口(eg:20880);
- registry会帮我们注册服务,把服务的地址保存到注册中心,每一个url地址的调用会来保存它实际的invoker(执行器);
当我们暴露服务的时候:
- 我们要获取到invoker:getInvoker()(获取到执行器);
- 我们使用Protocol来暴露执行器;
- 而Protocol会使用两个,一个是DubboProtocol,一个是RegistryProtocol,这两个protocol对应两个Exporter(暴露者);
- DubboProtocol:开启服务器;
- RegistryProtocol:把:每一个服务和对应的url地址信息,都保存在注册表(ProviderConsumerRegTable)里面。注册表里面缓存了:一个url地址,和对应的执行器。执行器里面有真正的服务。通过url地址,我们可以拿到执行器,就可以完成服务的调用了;