生成URL
dubbo的功能,就是一个RPC框架,那么自然最重要的就是服务的发布,和服务的调用。
现在我们只有一个xml,然后spring帮我们读取生成ServiceBean,所以现在我们只有一个接口名字,需要暴露的实现类
...
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
</beans>
dubbo的启动流程中,首先是启动spring的容器,加载bean,然后加载DubboBootStrap类,实例化,连接注册中心,最后才开始调用服务暴露,也就是ServiceBean父子系统的export()方法。
dubbo服务暴露的入口在DubboBootStrap类start()方法中,在调用export()方法之前,回进行注册中心的连接,所以dubbo的启动,核心是在DubboBootStrap类中。
调用ServiceConfig的export()方法,【注意】,调用一次export()就是暴露一个接口服务,如果有多个服务需要暴露,则在DubboBootStrap回多次调用export()方法,所以我们直接从ServiceConfig开始,看一下继承图,七层妖塔
看一下ServiceConfig的成员,内容不多,其实大部分字段放 在了父类ServiceConfigBase里面
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//默认实现 JavassistProxyFactory
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private transient volatile boolean exported; //服务是否暴露
private transient volatile boolean unexported;//默认为false
private DubboBootstrap bootstrap;
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(); //The exported services
父类ServiceConfigBase字段:
protected String interfaceName; //暴露服务接口 名
protected Class<?> interfaceClass; //暴露服务接口 Class对象
protected T ref; //接口 实现
protected String path; //服务 名字
protected ProviderConfig provider; //服务提供者的配置
protected String providerIds;
protected volatile String generic; //通用service
protected ServiceMetadata serviceMetadata;
服务暴露入口:此处,已经获得了service的所有信息,在下面的set方法中注入,包括Class信息,名字等等。
public synchronized void export() {
if (!shouldExport()) { return; }
//启动引导类
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
checkAndUpdateSubConfigs(); // ★ 1
//init serviceMetadata
serviceMetadata.setVersion(getVersion());
serviceMetadata.setGroup(getGroup());
serviceMetadata.setDefaultGroup(getGroup());
serviceMetadata.setServiceType(getInterfaceClass());
serviceMetadata.setServiceInterfaceName(getInterface());
serviceMetadata.setTarget(getRef());
if (shouldDelay()) { //是否需要延迟 暴露
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
doExport(); //★ 2【入】
}
exported();
}
1.checkAndUpdateSubConfigs()
各种配置信息检测(代码很长。省略异常部分)
private void checkAndUpdateSubConfigs() {
//下面三个方法都在 父类ServiceConfigBase中
completeCompoundConfigs();
checkDefault();
checkProtocol();
// init some null configuration. SPI机制
List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class)
.getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
configInitializers.forEach(e -> e.initServiceConfig(this));
// 如果协议不止injvm 那么就要检测
if (!isOnlyInJvm()) {
checkRegistry();
}
this.refresh(); //顶层父类AbstractConfig 中的方法。
if (StringUtils.isEmpty(interfaceName)) { //服务接口名字不能为空
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
//-----检测 ref 是否为泛化服务类型
if (ref instanceof GenericService) {
interfaceClass = GenericService.class; // 设置 interfaceClass 为 GenericService.class
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString(); // 设置 generic = "true"
}
} else {
//反射获取接口 Class对象
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
// 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
checkInterfaceAndMethods(interfaceClass, getMethods()); //这个方法在父类中
checkRef(); //检测接口是否实现
generic = Boolean.FALSE.toString(); // 设置 generic = "false"
}
// local 和 stub 在功能应该是一致的,用于配置本地存根
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
// 获取本地存根类
localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
// 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);
}
// 检测各种对象是否为空,为空则新建,或者抛出异常
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
ConfigValidationUtils.validateServiceConfig(this);
postProcessConfig();
}
2.doExport()
服务暴露中,它的流程基本就是一条流水线,它的回调很少,基本都是往前走)内容比较简单,只是一个入口。
protected synchronized void doExport() {
if (exported) {
return;
}
exported = true; //暴露打开了!
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
//暴露服务 【入】
doExportUrls(); //★ 3
}
3. doExportUrls()
思路:
1.获得普通的所有service,
2.然后先将本次需要暴露的service加入到仓库中,然后再根据服务是消费者还是提供者,再一次放入分类的仓库中
3.根据协议数量,一次按协议进行暴露
private void doExportUrls() {
//3.1 service仓库
ServiceRepository repository = ApplicationModel.getServiceRepository();
//3.2 service描述
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
//3.3 注册providerModel
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);
//3.4 加载 注册中心链接
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
//遍历协议,在每个协议下,暴露服务
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(pathKey);
doExportUrlsFor1Protocol(protocolConfig, registryURLs); //【入】★4号
}
}
3.1 ServiceRepository
从它的名字,自然可以看出来,它就是一个容器,它把service分为了四类保存,
public class ServiceRepository extends LifecycleAdapter implements FrameworkExt {
// services
private ConcurrentMap<String, ServiceDescriptor> services = new ConcurrentHashMap<>();
// consumers
private ConcurrentMap<String, ConsumerModel> consumers = new ConcurrentHashMap<>();
// providers
private ConcurrentMap<String, ProviderModel> providers = new ConcurrentHashMap<>();
// useful to find a provider model quickly with serviceInterfaceName:version
private ConcurrentMap<String, ProviderModel> providersWithoutGroup = new ConcurrentHashMap<>();
//将 接口Class对象,封装 放入缓冲中
public ServiceDescriptor registerService(String path, Class<?> interfaceClass) {
ServiceDescriptor serviceDescriptor = registerService(interfaceClass); //将接口Class对象 封装
//路径 不相同 才放入...
if (!interfaceClass.getName().equals(path)) {
services.putIfAbsent(path, serviceDescriptor);
}
return serviceDescriptor;
}
}(省略版)
3.2 ServiceDescriptor 服务封装
它的组成成分:1.名字,2.Class对象,3.方法列表(包括了重载)
public class ServiceDescriptor {
private final String serviceName;
private final Class<?> serviceInterfaceClass;
// 方法name-方法descriptor List(因为一个name可能重载,出现多个实现)
private final Map<String, List<MethodDescriptor>> methods = new HashMap<>();
//增加 参数描述
private final Map<String, Map<String, MethodDescriptor>> descToMethods = new HashMap<>();
public ServiceDescriptor(Class<?> interfaceClass) {
this.serviceInterfaceClass = interfaceClass;
this.serviceName = interfaceClass.getName();
initMethods(); //方法描述类,初始化
}
private void initMethods() {
Method[] methodsToExport = this.serviceInterfaceClass.getMethods(); //反射获得所有方法
for (Method method : methodsToExport) { //遍历方法,封装放到容器中
method.setAccessible(true);
List<MethodDescriptor> methodModels = methods.computeIfAbsent(method.getName(), (k) -> new ArrayList<>(1));
methodModels.add(new MethodDescriptor(method));
}
//再来一个便于查找具体method 的容器
methods.forEach((methodName, methodList) -> {
Map<String, MethodDescriptor> descMap = descToMethods.computeIfAbsent(methodName, k -> new HashMap<>());
methodList.forEach(methodModel -> descMap.put(methodModel.getParamDesc(), methodModel));
});
}
}
我们简单看一下MethodDescriptor
public class MethodDescriptor {
private final Method method;
// private final boolean isCallBack;
// private final boolean isFuture;
private final String paramDesc; //参数类型 名字 合集
// duplicate filed as paramDesc, but with different format.
private final String[] compatibleParamSignatures;
private final Class<?>[] parameterClasses; // 参数类型 Class对象
private final Class<?> returnClass; //返回类型 Class对象
private final Type[] returnTypes; //楼上的封装
private final String methodName;
private final boolean generic;
...
}
3.3 registerProvider() 注册提供者
providerModel生成,内容简单,new出来,也就是对基础信息的包装,然后放到缓冲中,serviceKey是服务的唯一名字
public class ProviderModel {
private String serviceKey; //serviceId
private final Object serviceInstance; //service 实例
private final ServiceDescriptor serviceModel; //接口描述类
private final ServiceConfigBase<?> serviceConfig; //启动,暴露类
private final List<RegisterStatedURL> urls; //暴露的路径,
...
}
梳理一下三者的关系:
服务注册的仓库,也就是总容器:serviceRepository,它的里面存放四个内容:serviceDescriptor, ConsumerModel, ProviderModel,ProvidersWithoutGroup
serviceDescriptor是service接口的包装类,它里面有method的信息,
ProviderModel是 service提供者的包装,里面有serviceId,实现类,暴露类,接口描述类等等字段。
注册完毕之后的状态:红色箭头是新增的,也就上方两个方法注册进入的:
3.4 loadRegistries() 加载注册中心链接
传入的参数:ServiceConfig实例,true,这个和我们的配置信息有关,dubbo不仅支持多协议,而且注册多注册中心,所以这里的注册中心链接是list集合,我们demo配置了一个注册中心,所以size=1。
//加载注册中心 链接 参数:ServiceCOnfig ,true
public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
ApplicationConfig application = interfaceConfig.getApplication(); //获得配置 都集成自AbstactConfig类
List<RegistryConfig> registries = interfaceConfig.getRegistries(); //注册信息
// 解析得到URL
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE; //默认地址
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
//添加形体 到map中
AbstractConfig.appendParameters(map, application);
AbstractConfig.appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
AbstractInterfaceConfig.appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
//生成URL,这里产生的是一个列表,解析得到 URL 列表,address 可能包含多个注册中心 ip,
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
//将url 协议头设置为registry,
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(extractRegistryType(url))
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url); // ★ 添加
}
}
}
}
}
return registryList;
}
注册中心配置超级多,我们简单看一下:RegistryConfig类
public class RegistryConfig extends AbstractConfig {
private String address; //注册中心 地址
private String username; //登录注册中心
private String password;
private Integer port; //注册断开
private String protocol;
private String transporter; //网络通信方式
private String server;
private String client;
....
}
而ApplicationConfig,就是 相对于注册中心来说的 客户端 信息。
获取到注册中心列表:
最后进行所有协议遍历,进行服务暴露:demo中的协议信息: