平时我要了解一个框架,基本会去从他的Listener入手,如果web.xml中没有配置listener可能还会有 filter,这是spring给我们的启示,可是当要去了解dubbo的时候,发现dubbo并没有自己的listener监听器。已知dubbo是一款和spring结合较好的rpc框架,那么其不使用web容器相关的方式,必然遵循spring的方式。依据平时开发经验,我们知道要想在Spring初始化之后,做一些自己的逻辑,有一种方法即实现org.springframework.beans.factory.InitializingBean
接口,那么我们搜一下,dubbo有没有这么干。
前文 Dubbo 对配置文件的解析我们知道,服务(beans->dubbo:service)相关的节点配置会在com.alibaba.dubbo.config.spring.ServiceBean
的实例中保存,这里正好巧了public class ServiceBean
出去一些初始化参数的工作,这里我们主要关注这里
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && ! isExported() && ! isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
//暴露服务
export();
}
}
}
public synchronized void export() 方法里会做一些判断,我们这里只关注自己最关注的部分
public synchronized void export() {
//.......如果当前服务被暴露过就不再暴露,等一些判断
if (delay != null && delay > 0) {//delay 延迟暴露参数,如果配置延迟暴露。
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport();
}
}
很尴尬,好多源码都没有相关的文档注释,如果没参考相关资料,第一次阅读会很难受
protected synchronized void doExport() 这个方法的作用大概是执行具体的服务暴露
protected synchronized void doExport() {
//......异常处理省略.......//
//检查provider是否有配置,如果该属性为null,则new一个ProviderConfig对象,方法内部调用appendProperties(provider)方法,该方法内部会拼装一个 dubbo.tagName.属性名的key,在配置文件中查找值,如果有值则调用属性的setter方法,设置属性值。
checkDefault();
//如果ProviderConfig实例不为null的情况下,初始化一些配置,通常情况下很少会配置 标签
//提供方的缺省值,当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值,可选。
if (provider != null) {
//application 对应 应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
if (application == null) {
application = provider.getApplication();
}
// module 对应 模块配置,用于配置当前模块信息,可选。
if (module == null) {
module = provider.getModule();
}
//registries 对应 注册中心配置,用于配置连接注册中心相关信息。
if (registries == null) {
registries = provider.getRegistries();
}
// monitor 对应 监控中心配置,用于配置连接监控中心相关信息,可选。
if (monitor == null) {
monitor = provider.getMonitor();
}
//protocols 对应 协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
if (protocols == null) {
protocols = provider.getProtocols();
}
}
//........省略进一步初始化相关配置的代码行 .......//
if (ref instanceof GenericService) { //判断是否是泛化调用接口,这里不重要。
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查接口类中是否存在指定的方法,如果dubbo:service->dubbo:method 没有配置的情况下,methods为null,该方法不会执行方法校验。如果有相关的配置,该方法会检查name属性对应的方法是否存在,不存在会抛IllegalStateException异常。
checkInterfaceAndMethods(interfaceClass, methods);
// 检查服务实现,如果ref不是服务的接口实现,则会抛出IllegalStateException异常。
checkRef();
// 声明该接口非泛化调用
generic = Boolean.FALSE.toString();
}
if(local !=null){ //如果是本地服务,常用配置方案下,local 值为null
if(local=="true"){
local=interfaceName+"Local";
}
Class> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(localClass)){
throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if(stub !=null){//如果是远程服务,常用配置方案下,stub值为null
if(stub=="true"){
stub=interfaceName+"Stub";
}
Class> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(stubClass)){
throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
//检查应用配置,如果没有配置会创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。检查失败会抛出IllegalStateException异常
checkApplication();
//检查注册中心配置,如果没有配置,则会检查配置文件中的dubbo.registry.address 设置,并创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。检查失败会抛出IllegalStateException异常
checkRegistry();
//检查协议配置,该方法不会抛出异常,如果没配置dubbo:protocol,会取dubbo:provider相关配置填充,如果依旧protocols依旧为null,则创建默认对象。其内部会调用appendProperties(AbstractConfig config) 填充属性。
checkProtocol();
//填充属性 不多说
appendProperties(this);
//
checkStubAndMock(interfaceClass);
// 初始化path属性
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 暴露服务的URL
doExportUrls();
}
doExportUrls(); 实现如下(吐槽,怀疑下到假的源码了,到处都没有文档注释):
@SuppressWarnings({ "unchecked", "rawtypes" })
private void doExportUrls() {
//获得注册中心列表
List registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
protected List loadRegistries(boolean provider) 类相关解析
protected List loadRegistries(boolean provider) {
// 检查注册中心
checkRegistry();
List registryList = new ArrayList();
if (registries != null && registries.size() > 0) {
for (RegistryConfig config : registries) {
//一般这个拿到的是注册中心地址,例如:zookeeper://1.6.5.5:2181
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (address != null && address.length() > 0
&& ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map map = new HashMap();
appendParameters(map, application);
appendParameters(map, config);
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (! map.containsKey("protocol")) {
//查询注册中心是否支持remote协议
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
//目前到这里是zookeeper://ip地址:2181/com.alibaba.dubbo.registry.RegistryService?application=配置应用名&dubbo=2.8.4&file=缓存文件路径&logger=log4j&organization=22222&owner=weiythi&pid=20772×tamp=1510040471140
List urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
//这个url会被转换成这种格式 registry://ip:2181/com.alibaba.dubbo.registry.RegistryService?application=配置的应用名&dubbo=2.8.4&file=缓存文件路径&logger=log4j&organization=22222&owner=weiythi&pid=20772®istry=zookeeper×tamp=1510040471140 即会将注册中心转换成registry参数放到url中,并将url的protocol设置成registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
//获得协议信息,默认dubbo
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
String host = protocolConfig.getHost();
if (provider != null && (host == null || host.length() == 0)) {
host = provider.getHost();
}
boolean anyhost = false;
//判断host是不是为null,或localhost,等本地ip地址
if (NetUtils.isInvalidLocalHost(host)) {
//......省略 使用各种方式获取本机的实际ip地址........//
host = InetAddress.getLocalHost().getHostAddress();
//......省略 使用各种方式获取本机的实际ip地址........//
}
Integer port = protocolConfig.getPort();
//.....省略,如果port为null,则尝试使用默认的port,如果默认的port无法使用,则会尝试使用随机的端口
Map map = new HashMap();
if (anyhost) {
map.put(Constants.ANYHOST_KEY, "true");
}
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// appendParameters 其实就是把属性值转到map中,这里不做说明
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 常用配置方案下,methods为null,上文代码注释中有说明
if (methods != null && methods.size() > 0) {
//......省略,当有暴露指定方法的配置时,会暴露指定方法
}
if (ProtocolUtils.isGeneric(generic)) {
map.put("generic", generic);
map.put("methods", Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
//获取接口中所有的方法名
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 {
//拼接方法名为 a,b,c,d 这种形式
map.put("methods", StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
}
}
if (! ConfigUtils.isEmpty(token)) { //处理token
if (ConfigUtils.isDefault(token)) {
map.put("token", UUID.randomUUID().toString());
} else {
map.put("token", token);
}
}
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 导出服务
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// 注意这里的URL是com.alibaba.dubbo.common.URL 对象,最后会被处理成一个类似这样的格式,以dubbo协议为例
//dubbo://ip:port/接口名?其他信息
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置为none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {//遍历注册中心
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
//ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
下一篇集中处理如下代码流程,这一篇只是初步了解了dubbo 服务暴露预处理的一些逻辑,
至于为什么dubbo会选择生成 registry:// 和 dubbo:// 如下两种链接。以后篇幅讨论.
后续将继续跟踪以下代码片段的实现。
//ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。
Invoker> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。
Exporter> exporter = protocol.export(invoker);
exporters.add(exporter);