遗留问题
- 在serviceConfig中,导出服务等级分为3中,一次为exportLocal(url)、在应用范围内使用;一在没有配置注册中心时(直连模式),如下代码;一种是配置注册中心时,服务注册至服务中心。
//serviceConfig.611line
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
- 微服务调用和导出中涉及到的Netty通信的部分。
- 在SPI机制中,有动态生成的代码来看,调用的最终方法是由URL中参数所映射的子类中的方法。在实际调用中,会生成一个调用链,分别调用Protocol$Adaptive(SPI动态生成类)->ProtocolListenerWrapper->QosProtocolWrapper->ProtocolFilterWrapper->InjvmProtocol
详细见Dubbo SPI高级用法之 AOP
1. 服务提供方Demo:
package org.apache.dubbo.demo.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Provider {
public static void main(String[] args) throws Exception {
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"resources/META-INF/spring/dubbo-demo-provider.xml"});
context.start();
System.out.println("Provider started.");
System.in.read(); // press any key to exit
}
}
其中的代码主要是加载配置文件,Dubbo服务导出充分利用了Spring容器的相关功能。服务提供方的配置文件dubbo-demo-provider.xml 如下:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/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-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<!-- use multicast registry center to export service -->
<dubbo:registry address="N/A"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>
2. 导出服务
服务提供方特有的 用于解析配置文件的类主要有: ProviderConfig,RegistryConfig,ServiceBean,用于存储配置文件的值。其中ServiceBean的继承结构为:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware, ApplicationEventPublisherAware {
其实现了ApplicationListener,因此Spring实例化Bean最后,会调用其中的onApplicationEvent方法。原因可以看文档Spring中ApplicationContext对Bean的管理,onApplicationEvent的方法如下:(配置文件中的每一个dubbo:service都会有一个ServiceBean实例对应,因此下面的方法的颗粒的为:一个dubbo:service,可能是多个dubbo:protocol,多个dubbo:registry标签)
// ServiceBean.onApplicationEvent
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
// ServiceBean.export()
@Override
public void export() {
// 导出服务
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
父类的export方法为ServiceConfig的export的方法。代码如下:
//ServiceConfig.export
public synchronized void export() {
//核对相关的配置参数
checkAndUpdateSubConfigs();
//是否需要导出
if (!shouldExport()) {
return;
}
//是否需要延时导出
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//导出
doExport();
}
}
// ServiceConfig.doExport()
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
//防止重复导出
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
// ServiceConfig.doExportUrls
@SuppressWarnings({"unchecked", "rawtypes"})
private void doExportUrls() {
//获取所有服务注册中心的地址
List<URL> registryURLs = loadRegistries(true);
//protocol中有服务暴露协议与端口,支持多注册地址,多协议暴露服务
for (ProtocolConfig protocolConfig : protocols) {
//pathKey = org.apache.dubbo.demo.DemoService
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
//prividerMode中存放需要导出服务的信息,比如接口、实例和名字,其中还有一个
//prividerMethdeMode存放服务的所有的方法,
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
//上述需要导出的服务信息保存一份在PROVIDED_SERVICES中,是一个线程安全map,且有static修饰,所有服务共享
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
//ServiceConfig.doExportUrlsFor1Protocol
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
//把所有的参数保存在map中
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
//如果配置文件中制定了导出方法,咋把导出方法的参数保存在map中
if (CollectionUtils.isNotEmpty(methods)) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
List<ArgumentConfig> arguments = method.getArguments();
if (CollectionUtils.isNotEmpty(arguments)) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
// one callback in the method
if (argument.getIndex() != -1) {
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) {
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
if (ProtocolUtils.isGeneric(generic)) {
map.put(GENERIC_KEY, generic);
map.put(METHODS_KEY, ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
// export service
//获得服务导出的ip地址和端口(169.254.72.106 20880)
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(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(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
//2.1把所有非remote的方法导出一份至本地
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//2.2 注册中心服务发布
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//2.3无注册中心服务发布
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
}
this.urls.add(url);
}
### 2.1导出服务至本地(调用不走网络请求)
//ServiceConfig.exportLocal
@SuppressWarnings({"unchecked", "rawtypes"})
/**
* always export injvm
*/
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)//LOCAL_PROTOCOL = injvm
.setHost(LOCALHOST_VALUE) //LOCALHOST_VALUE = 127.0.0.1
.setPort(0)
.build();
/* URL为
injvm://127.0.0.1/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=169.254.72.106&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1852®ister=true&release=2.7.3&side=provider×tamp=1577867068615
*/
//protocal 为spi 加载机制,在传入的URL为injvm时,下面调用的是InjvmProtocol.export方法
Exporter<?> exporter = protocol.export(
//JavassistProxyFactory.getInvoker;
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
//JavassistProxyFactory.getInvoker
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
因此返回的是一个AbstractProxyInvoker,其中保存了type(服务接口),proxy(接口实现类的实例),URL(调用路径)等信息。
依据Wrapper扩展点加载机制,在执行InjvmProtocol之前需求先执行ProtocolListenerWrapper->QosProtocolWrapper->ProtocolFilterWrapper->InjvmProtocol。其中
ProtocolListenerWrapper.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), EXPORTER_LISTENER_KEY)));
}
返回的是ListenerExporterWrapper,底层在完成导出服务之后,会调用所有的ExporterListener的实现类的export方法,暂时没有实现类,通过这种方式给 在导出服务之后业务自定义处理提供了入口,只要提供实现ExporterListener类就好。
//QosProtocolWrapper.export
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
startQosServer(invoker.getUrl());
return protocol.export(invoker);
}
return protocol.export(invoker);
}
QosProtocolWrapper对通过网络注册和调用提供了服务质量保证。
//ProtocolFilterWrapper.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
//buildInvokerChain构建调用链
return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
//InjvmProtocol.export
//exporterMap = new ConcurrentHashMap<String, Exporter<?>>();
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
//InjvmExporter.class
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}
@Override
public void unexport() {
super.unexport();
exporterMap.remove(key);
}
总结:ServiceConfig.exporters中保存了所有导出来的服务,其中导出范围在jvm中实例是一个ListenerExporterWrapper,这是一个装饰类,其中的exporter保存了InjvmExporter的实例,InjvmExporter的实例保存了JavassistProxyFactory.getInvoker返回的实际调用的Invoker;另外在AbstractProtocol中存在一个ConcurrentHashMap,保留了服务名字和InjvmExporter。
2.2 注册中心服务发布
发布代码如下:
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//2.2 注册中心服务发布
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
invoker 是一个JavassistProxyFactory.getInvoker()的返回,是AbstractProxyInvoker,其中保存了服务的接口,实现类实例和Url,不过此处的URL是配置文件中 dubbo:registry 标签中对应的参数。wrapperInvoker 是一个装饰类,保存了目标invoker。在调用protocol.export(wrapperInvoker);同样需要先调用ProtocolListenerWrapper->QosProtocolWrapper->ProtocolFilterWrapper。最后调用RegistryProtocol.export();代码如下:
//RegistryProtocol.export()
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
// getSubscribedOverrideUrl修改了导出服务的协议,由dubbo ->provider
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
//
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
2.3 无注册中心服务发布
发布代码如下:
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
invoker 是一个JavassistProxyFactory.getInvoker()的返回,是AbstractProxyInvoker,其中保存了服务的接口,实现类实例和URL.wrapperInvoker 是一个装饰类,保存了目标invoker。protocol.export()最终调用的是DubboProtocol,代码如下:
//DubboProtocol.export();
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);//org.apache.dubbo.demo.DemoService:20880
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//调用Netty相关程序,绑定端口和处理程序
openServer(url);
optimizeSerialization(url);
return exporter;
}
总结:exporters中保存的是ProtocolListenerWrapper.export()的返回,返回的是ListenerExporterWrapper,这是一个装饰类,里面保存了导出结束后监听程序List。还有真正导出DubboExporter的实例。DubboExporter依然是一个装饰类,其中保存了一个map和AbstractProxyInvoker。