注册服务
经过上面的源码分析,我们知道了dubbox如何进行配置解析,下面看看如何定义一个服务。
- 定义服务:修改 dubbox-first-demo.xml
<?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-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="test-services" owner="linxm" organization="workhouse"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="rest" port="8888" contextpath="services" />
<dubbo:protocol name="dubbo" serialization="kryo" />
<dubbo:service interface="me.helllp.demo.dubboxStudy.api.Hello" ref="helloService" protocol="dubbo" />
<bean id="helloService" class="me.helllp.demo.dubboxStudy.api.impl.HelloImpl" />
</beans>
这里注意:我们需要启动zookeeper服务!
在pom.xml中增加zookeeper client的依赖
<!-- 增加zookeeper客户端 -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
- 书写服务接口和实现
public interface Hello {
public String showMessage();
}
public class HelloImpl implements Hello{
@Override
public String showMessage() {
return "显示信息";
}
}
- 我们部署Dubbo Admin来查看一下效果
源码分析
基于上一篇的讨论,我们在解析配置文件的dubbo:service节点的时候,会初始化com.alibaba.dubbo.config.spring.ServiceBean
让我们看看ServiceBean都干了什么,下面是代码节选
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
if (applicationContext != null) {
SPRING_CONTEXT = applicationContext;
try {
Method method = applicationContext.getClass().getMethod("addApplicationListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
method.invoke(applicationContext, new Object[] {this});
supportedApplicationListener = true;
} catch (Throwable t) {
if (applicationContext instanceof AbstractApplicationContext) {
try {
Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
if (! method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(applicationContext, new Object[] {this});
supportedApplicationListener = true;
} catch (Throwable t2) {
}
}
}
}
}
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();
}
}
}
}
- ServiceBean实现了ApplicationContextAware接口,在初始化的时候会这些接口方法setApplicationContext
- setApplicationContext方法中通过反射执行springContext的addApplicationListener方法,将serviceBean作为参数,加入监听队列中(ServiceBean同时还实现了ApplicationListener接口)。
- spring容器初始化过程中会向所有的ApplicationListener接口实现发生触发消息,onApplicationEvent方法会执行
- onApplicationEvent方法中主要执行父类ServiceConfig的export方法。
- export方法主要执行ServiceConfig的doExport方法,这个方法中才是注册服务的真正代码。
doExport方法分析
主要的调用关系
- doExportUrls()
- doExportUrls()
- doExportUrlsFor1Protocol() 注册的逻辑
下面我们看看doExportUrlsFor1Protocol()方法的主要逻辑
// 导出服务
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
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);
}
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
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);
- 读取配置,生成URL
- 获取ConfiguratorFactory的针对指定协议的扩展(这里可以自定义扩展,下面会有一个演示)
- 本地服务注册和发布:exportLocal(url);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
- 远程服务注册和发布:
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
ConfiguratorFactory扩展
- 生成一个配置工厂
public class DemoDubboConfiguratorFactory implements ConfiguratorFactory{
@Override
public Configurator getConfigurator(URL url) {
System.out.println("==============这是一个自定义配置================");
return new DemoDubboConfigurator(url);
}
}
- 生成Configurator
public class DemoDubboConfigurator implements Configurator{
private URL url;
public DemoDubboConfigurator(URL url){
this.url = url;
}
@Override
public int compareTo(Configurator o) {
return 0;
}
@Override
public URL getUrl() {
return this.url;
}
@Override
public URL configure(URL url) {
// 这里是我们对URL进行定制的地方
return url.addParameter("demoName", "linxm");
}
}
- 书写扩展配置文件:com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory,你应该知道放在哪
dubbo=me.helllp.demo.dubboxStudy.listener.DemoDubboConfiguratorFactory
- 运行效果:日志中会输出==============这是一个自定义配置================
下一节我们将会研究URL如何注册到zookeeper的!