前言
我们在第2章节里面,已经讲到Dubbo的初始化流程。Dubbo的初始化是随着Spring容器Bean的实例化而进行的,今天我们重点看这样一个节点,它在配置文件中是这样的:<dubbo:service interface="com.viewscenes.netsupervisor.service.InfoUserService" ref="service" />
它会完成Dubbo服务暴露的逻辑,我们先看下大概流程。

一、开始
上述配置文件中的节点信息对应的处理类是ServiceBean。我们先看下它的结构
public class ServiceBean<T> extends ServiceConfig<T> implements
InitializingBean, DisposableBean, ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, BeanNameAware {}
我们可以看到,这个类实现了Spring的不同接口,这就意味着Spring在不同的时机就会调用到相应方法。
1、设置上下文
在Spring完成实例化和IOC之后,会调用到invokeAwareInterfaces方法,来判断Bean是否实现了Aware接口,然后调用对应的方法。
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});
method.invoke(applicationContext, new Object[]{this});
supportedApplicationListener = true;
} catch (Throwable t) {
//省略无关代码....
}
}
}
2、初始化方法
然后我们还看到它实现了InitializingBean接口,那么初始化方法afterPropertiesSet也是跑不掉呀。在这里面,就是拿到Dubbo中的应用信息、注册信息、协议信息等,设置到变量中。最后有个判断方法值得我们注意isDelay 当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
//......
}
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
//......
}
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
//......
}
if ((getRegistries() == null || getRegistries().isEmpty())) {
//......
}
if ((getProtocols() == null || getProtocols().isEmpty())
&& (getProvider() == null || getProvider().getProtocols() == null ||
getProvider().getProtocols().isEmpty())) {
//......
}
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
if (!isDelay()) {
export();
}
}
3、开始暴露
这个方法是在Spring 上下文刷新事件后被调用到,它是服务暴露的入口方法。
public void onApplicationEvent(ContextRefreshedEvent event) {
//是否有延迟暴露 && 是否已暴露 && 是不是已被取消暴露
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
//暴露服务
export();
}
}
二、准备工作
Dubbo在暴露服务之前,要检查各种配置 ,设置参数信息,还要补充一些缺省的配置项,然后封装URL对象信息 。在这里,我们必须重视URL对象,Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。
1、检查配置
export()方法在服务暴露之前,有两个配置项要检查。是否暴露服务以及是否延迟暴露服务。
public synchronized void export() {
// 获取 export 和 delay 配置
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 如果 export 为 false,则不暴露服务
if (export != null && !export) {
return;
}
// delay > 0,延时暴露服务
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
}else {
doExport();
}
}
很显然,如果我们不想暴露服务,这样可以来配置:<dubbo:provider export="false"/>
同样的,如果我们想延迟暴露服务,就可以这样来配置它:<dubbo:provider delay="100"/>
在Dubbo决定要暴露服务之后,还要做一些事情。
检查服务接口合法性
检查Conifg核心配置类等是否为空,为空就从其他配置中获取相应实例
区分泛化服务和普通服务
检查各种对象是否为空,创建或抛出异常等
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("
<dubbo:service interface=\"\" /> interface not allow null!");
}
checkDefault();
if (provider != null) {
//检查配置对象是否为空并赋值
}
if (module != null) {
//检查配置对象是否为空并赋值
}
if (application != null) {
//检查配置对象是否为空并赋值
}
//区分泛化类和普通类
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
//返回接口Class对象并检查
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = Boolean.FALSE.toString();
}
//检查各种对象是否为空,创建或抛出异常
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
//服务暴露
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
2、多协议多注册中心
2.1、配置
Dubbo 允许我们使用不同的协议暴露服务,也允许我们向多个注册中心注册服务。
比如我们想同时使用dubbo、rmi两种协议来暴露服务:
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:protocol name="rmi" port="1099" />
又或者我们需要把服务注册到zookeeper、redis:
<dubbo:registry address="zookeeper://192.168.139.129:2181"/>
<dubbo:registry address="redis://192.168.139.129:6379"/>
我们这样配置后,在zookeeper、redis里面都会保存基于两种协议的服务信息。
首先在redis中我们执行命令:hgetall /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers
得到以下结果:
1) "rmi://192.168.100.74:1099/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider×tamp=1545804399128"
2) "1545805385379"
3) "dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider×tamp=1545804391176"
4) "1545805385379"
同时,我们在zookeeper中执行命令:ls /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers
得到以下结果:
[dubbo%3A%2F%2F192.168.100.74%3A20880%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804391176,
rmi%3A%2F%2F192.168.100.74%3A1099%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804399128]
2.2、多注册中心
首先是多个注册中心的加载,loadRegistries方法返回一个List<URL> registryURLs。基于上面的配置文件,这里的registries就是一个长度为2的List<RegistryConfig>,最终将它们解析为List<URL> registryURLs。
protected List<URL> loadRegistries(boolean provider) {
//检查配置
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (address == null || address.length() == 0) {
// 若 address 为空,则将其设为 0.0.0.0
address = Constants.ANYHOST_VALUE;
}
// 从系统属性中加载注册中心地址
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
//检查address合法性以及从封装map配置信息
if (address != null && address.length() > 0
&& !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
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")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).
hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
//将配置信息封装成URL对象
List<URL> urls = UrlUtils.