Dubbo源码学习--服务发布(ServiceBean、ServiceConfig)
2017-12-05 10:44
531 查看
前面讲过Dubbo SPI拓展机制,通过ExtensionLoader实现可插拔加载拓展,本节将接着分析Dubbo的服务发布过程。
以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码。在
本人搭建了一个zookeeper服务器,将注册中心由multicast更改为zookeeper。可是,在Spring中并没有定义这些配置节点,配置文件中的内容如何自动加载到内存中、赋值给相应的对象呢?
定义配置文件相应Java Bean
编写XSD文件
实现BeanDefinitionParser接口和继承NamespaceHandlerSupport抽象类
编写handlers和schemas文件,串联各部分
关于Spring可拓展Schema机制,请自行Google了解。
在
通过运用Spring Schema机制,实现对自定义配置文件参数注入后,会继续执行
实际源码篇幅过长,这里只截取关键的核心部分。通过上述源码片段,可以看到
在加载完自定义配置文件属性后,会执行
具体的服务发布过程交给
源码中,通过
方法大体流程如下:
map装配参数
利用map中的参数构建URL,为暴露服务做准备。此URL为Dubbo自定义类型,是final类,实现了Serializable接口
根据范围选择是暴露本地服务,还是暴露远程服务
根据代理工厂生成服务代理invoker
根据配置的协议,暴露服务代理
值得注意的是:在判断是否暴露本地服务和远程服务时,有个简单的逻辑值得学习,用得很漂亮,使得代码变得很简洁。提取核心部分简化如下:
默认情况下,当scope为null的时候,会同时暴露本地服务和远程服务。这个小巧的逻辑技巧值得学习!
经过以上分析:可以大体了解Dubbo RPC服务的发布过程,但是在整个流程中具体是如何产生服务代理的呢?请听下回分解:Dubbo RPC服务的发布之服务代理
以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码。在
dubbo-demo-provider模块下配置文件
dubbo-demo-provider.xml中定义了服务提供方、注册中心、协议及端口、服务接口等信息,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" 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="demo-provider"/> <!-- 使用multicast广播注册中心暴露服务地址 --> <!--<dubbo:registry address="multicast://224.5.6.7:1234"/>--> <dubbo:registry address="zookeeper://192.168.1.197:2181"/> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- 和本地bean一样实现服务 --> <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/> </beans>
本人搭建了一个zookeeper服务器,将注册中心由multicast更改为zookeeper。可是,在Spring中并没有定义这些配置节点,配置文件中的内容如何自动加载到内存中、赋值给相应的对象呢?
dubbo-demo-provider模块
com.alibaba.dubbo.demo.provider.Provider类中
main方法作为入口,进行断点跟踪。可发现,Dubbo配置文件加载是基于Spring可拓展Schema自定义实现的。Spring可拓展Schema需要实现如下几步:
定义配置文件相应Java Bean
编写XSD文件
实现BeanDefinitionParser接口和继承NamespaceHandlerSupport抽象类
编写handlers和schemas文件,串联各部分
关于Spring可拓展Schema机制,请自行Google了解。
在
dubbo-config子模块
dubbo-config-spring中定义了
spring.handlers、
spring.schemas、
dubbo.xsd、
DubboBeanDefinitionParser、
DubboNamespaceHandler文件,配置的Java Bean是在
dubbo-config-api模块中定义的。
spring.handlers配置定义了 Dubbo名空间的处理类,
spring.schemas定义了XSD文件的位置,
DubboBeanDefinitionParser实现了
BeanDefinitionParser接口,
DubboNamespaceHandler继承了
NamespaceHandlerSupport抽象类。
spring.handlers文件内容如下:
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
spring.schemas文件内容如下:
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
通过运用Spring Schema机制,实现对自定义配置文件参数注入后,会继续执行
InitializingBean接口的
afterPropertiesSet() throws Exception方法。实例对象初始化完成后会执行事件监听器
ApplicationListener接口的
void onApplicationEvent(E event)方法。在Dubbo中,
ServiceBean类实现了
ApplicationListener接口方法。
ServiceBean类如下所示:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware { ...... public ServiceBean() { super(); this.service = null; } public ServiceBean(Service service) { super(service); this.service = service; } ...... 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(); } } } ...... @SuppressWarnings({"unchecked", "deprecation"}) public void afterPropertiesSet() throws Exception { ...... if (!isDelay()) { export(); } } }
实际源码篇幅过长,这里只截取关键的核心部分。通过上述源码片段,可以看到
ServiceBean实现了
ApplicationListener接口、
InitializingBean接口,同时也继承了
ServiceConfig类。在构造器方法中均调用了父类
ServiceConfig的构造方法。
在加载完自定义配置文件属性后,会执行
afterPropertiesSet方法,根据配置文件中delay属性判断是否立即执行
export方法。delay属性是用于标识是否延迟暴露服务,Dubbo中默认延迟暴露。若服务延迟暴露,则继续初始化示例对象,待对象初始化完成后,执行
onApplicationEvent方法,此时会执行
export()。
export()是在父类
ServiceConfig中定义的,是一个同步方法。进入
export()才是真正的开启服务发布之旅。
export()源码如下:
public synchronized void export() { if (provider != null) { if (export == null) { export = provider.getExport(); } if (delay == null) { delay = provider.getDelay(); } } if (export != null && !export) { return; } if (delay != null && delay > 0) { delayExportExecutor.schedule(new Runnable() { public void run() { doExport(); } }, delay, TimeUnit.MILLISECONDS); } else { doExport(); } }
export()方法通过关键字synchronized实现同步处理。并且实现了立即发布和延迟发布,并通过定时器来实现延迟发布,延迟发布时间单位是分钟。
具体的服务发布过程交给
doExport()处理,
doExport()也是一个同步方法,通过synchronized关键字实现。追踪源码,发现
doExport()仅仅是做了服务发布的前期准备工作,实际的发布工作交给
doExportUrls()方法来完成。
doExportUrls()方法源码如下:
@SuppressWarnings({"unchecked", "rawtypes"}) private void doExportUrls() { List<URL> registryURLs = loadRegistries(true); for (ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
源码中,通过
loadRegistries(boolean provider)方法将所有服务URL封装为List,得到服务提供者集合registryURLs。然后,通过for方法遍历,调用
doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)方法来实现对服务的发布。
doExportUrlsFor1Protocol方法源码如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { String name = protocolConfig.getName(); if (name == null || name.length() == 0) { name = "dubbo"; } Map<String, String> map = new HashMap<String, String>(); 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, application); appendParameters(map, module); appendParameters(map, provider, Constants.DEFAULT_KEY); appendParameters(map, protocolConfig); appendParameters(map, this); ...... 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 { map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } if (!ConfigUtils.isEmpty(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(); } //获取注册监听地址和端口 String host = this.findConfigedHosts(protocolConfig, registryURLs, map); Integer port = this.findConfigedPorts(protocolConfig, name, map); //根据之前收集的map数据和地址端口,组装URL 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<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); Exporter<?> exporter = protocol.export(invoker); //此时加载的protocol为DubboProtocol对象 exporters.add(exporter); } } } this.urls.add(url); } @SuppressWarnings({"unchecked", "rawtypes"}) private void exportLocal(URL url) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(LOCALHOST) .setPort(0); Exporter<?> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); } }
方法大体流程如下:
map装配参数
利用map中的参数构建URL,为暴露服务做准备。此URL为Dubbo自定义类型,是final类,实现了Serializable接口
根据范围选择是暴露本地服务,还是暴露远程服务
根据代理工厂生成服务代理invoker
根据配置的协议,暴露服务代理
值得注意的是:在判断是否暴露本地服务和远程服务时,有个简单的逻辑值得学习,用得很漂亮,使得代码变得很简洁。提取核心部分简化如下:
//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务) if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { exportLocal(url); } //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露本地服务) if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) { ...... //exportRemote(url) ...... }
默认情况下,当scope为null的时候,会同时暴露本地服务和远程服务。这个小巧的逻辑技巧值得学习!
经过以上分析:可以大体了解Dubbo RPC服务的发布过程,但是在整个流程中具体是如何产生服务代理的呢?请听下回分解:Dubbo RPC服务的发布之服务代理
---
如果对您有帮助,不妨点个赞、关注一波 :)相关文章推荐
- Dubbo源码学习--服务发布(ProxyFactory、Invoker)
- Dubbo源码学习--服务是如何发布的
- dubbo源码:provider发布service服务一
- dubbo 源码学习笔记 (二) —— dubbo发布服务的过程
- dubbo源码:provider发布service服务三
- dubbo源码:provider发布service服务二
- Dubbo 源码学习(五)dubbo暴露服务的过程
- dubbo 源码学习笔记 (三) —— dubbo引用服务的过程
- 被事务代理的spring service 使用注解方式发布Dubbo服务
- dubbo源码分析-服务发布
- dubbo学习(三)-dubbo的服务发布
- Dubbo源码分析(十):服务发布
- DUBBO 源码解析(二) 服务的发布
- Dubbo源码学习--服务是如何引用的
- dubbo源码学习(五)dubbo暴露服务的过程
- 学习淘淘商城第十二课(发布dubbo服务)
- 被事务代理的spring service 不能使用注解方式发布dubbo服务
- 学习淘淘商城第十二课(发布dubbo服务)
- dubbo源码分析二:服务发布
- Dubbo2.7源码分析-如何发布服务