上一章讲了服务提供方启动的时候会扫描所有@Service注册,并注册相应的ServiceBean。
spring发送ContextRefreshedEvent后每个ServiceBean会通过自生的export方法暴露。
这章详细讲export怎么暴露服务,注册到注册中心。
简介
暴露服务时序
服务提供者暴露一个服务的过程图
ServiceBean.export
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
重点在调用父类的export方法
ServiceConfig.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() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
//立即暴露服务
doExport();
}
}
做了2件事
1.如果要进行延迟暴露,则开启一个子线程,在子线程中进行服务暴露的工作
2.否则立即暴露服务
doExport方法
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 (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
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();
}
//local属性已被弃用,由stub属性替代
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
//stub设为true,表示使用缺省代理类名,即:接口名 + Local后缀,服务接口客户端本地代理类名,用于在客户端执行本地逻辑,如本地缓存等,该本地代理类的构造函数必须允许传入远程代理对象,构造函数如:public XxxServiceLocal(XxxService xxxService)
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStub(interfaceClass);
checkMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
//暴露
doExportUrls();
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
做了几件事:
1. 对要暴露的服务进行了一系列的检查
检查provider,application,module,registries,monitor这些参数是否为空,是否是GenericService类型的服务,检查要注册的bean的引用和方法等。
2.调用doExportUrls方法进行暴露
doExportUrls方法
private void doExportUrls() {
//获取注册中心的配置
List<URL> registryURLs = loadRegistries(true);
//获取配置的服务暴露协义
for (ProtocolConfig protocolConfig : protocols) {
//根据不同协议将服务以url形式暴露
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
做了几件事:
1.获取注册中心的配置
2.获取配置的服务暴露协义
3.根据不同协议将服务以url形式暴露
doExportUrlsFor1Protocol方法
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//默认使用dubbo协议
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
//收集各类参数,放入map中,在为服务暴露做参数收集准备工作
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
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);
//解析服务接口的方法的特殊设置,我debug的时候这里直接是null
if (methods != null && !methods.isEmpty()) {
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 (arguments != null && !arguments.isEmpty()) {
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
//一个方法中单个callback
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 {
//一个方法中多个callback
// 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(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
//获取包装类,包装类实质上就是通过javassist生成的类,当我们通过包装类对角调dubbo接口时实际调的还是接口对象的原有方法。此处包装起到的作用无非是可以以统一的代码的去调用用户提供的不同的dubbo接口。这个wrapper其实就是我理解的RPC调用中的代理类。
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
//获取host(四种方式的获取,1.从配置中获取,2.返回本机的ip地址,3.获取注册中心的主机名,4.遍历本地网卡,返回第一个合理的IP)
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
//获取各个协义需要暴露的端口.获取优先级:
//Protocol的实现类的默认端口 ——> Protocol的配置端口 ——> 随机端口
Integer port = this.findConfigedPorts(protocolConfig, name, map);
//拼接URL。Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
//这里的ConfiguratorFactory是个扩展,可以用来设计自己的URL组成规则
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
//根据scope的配置决定是作本地暴露还是远程暴露,做服务暴露从结果上看就是产生了一个特定服务的 Exporter 类,并将其存储在对应的ServiceBean实例的 exporters属性中
String scope = url.getParameter(Constants.SCOPE_KEY);
// 配置为none不暴露
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// 配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// 如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
//只有远程暴露才需要用到注册中心url
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
//获取监控中心的URL,此处先暂不关心生成的细节,待到服务治理时才详细分析
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);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
//这里对参数做个介绍:
参数1:ref就是接口实现类
参数2:interfaceClass:接口类
参数3:在registryURL上添加参数,key为"export",value就是前面产生的服务协义的url
//proxyFactory的来历
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension()
这个类实例最终就是com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory类的实例,这是由于ProxyFactory类上
的注解决定的
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
//这个是用来没有注册中心情况下的暴露,应该是用于直连的场景
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
做了几件事:
1.收集各类参数,放入map中,在为服务暴露做参数收集准备工作
解析服务接口的方法的特殊设置,我debug的时候这里直接是null
获取包装类,包装类实质上就是通过javassist生成的类,当我们通过包装类对角调dubbo接口时实际调的还是接口对象的原有方法。此处包装起到的作用无非是可以以统一的代码的去调用用户提供的不同的dubbo接口
2.获取host
四种方式的获取:
protocolConfig.getHost() //从配置中获取
InetAddress.getLocalHost().getHostAddress() //返回本机的ip地址
registryURL.getHost() //获取注册中心的主机名
NetUtils.getLocalHost() //遍历本地网卡,返回第一个合理的IP
3.获取各个协义需要暴露的端口
获取优先级:
Protocol的实现类的默认端口 ——> Protocol的配置端口 ——> 随机端口
4.拼接URL
Dubbo框架是以URL为总线的模式,即运行过程中所有的状态数据信息都可以通过URL来获取。
比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的。
所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。
debug的时候看到的URL是:
dubbo://XXX.XXX.XXX.XX:20880/org.apache.dubbo.samples.api.client.HelloService?
anyhost=true
&application=dubbo-annotation-provider
&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService
&bind.ip=XXX.XXX.XXX.XX
&bind.port=20880
&default.timeout=1000
&dubbo=2.0.2
&generic=false
&interface=org.apache.dubbo.samples.api.client.HelloService
&methods=sayHello
&pid=6764
&side=provider
×tamp=1603444165134
5.根据scope的配置决定是作本地暴露还是远程暴露。
做服务暴露从结果上看就是产生了一个特定服务的 Exporter 类,并将其存储在对应的ServiceBean实例的 exporters属性中
配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
debug的时候会发现,先做本地暴露,再做远程暴露
本地暴露:exportLocal方法
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);
// ServiceClassHolder是用来保存当前服务接口实例ref对应的Class的,是一个简单的单例实现
StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
//把exporter加到一个list中
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
}
}
debug的时候看到这个URL local的值是:
injvm://127.0.0.1/org.apache.dubbo.samples.api.client.HelloService?
anyhost=true
&application=dubbo-annotation-provider
&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService
&bind.ip=XXX.XXX.XXX.XX
&bind.port=20880
&default.timeout=1000
&dubbo=2.0.2
&generic=false
&interface=org.apache.dubbo.samples.api.client.HelloService
&methods=sayHello
&pid=9504
&side=provider
×tamp=1603444872012
做了几件事
1.得到local的URL
2.把当前服务接口实例ref对应的class缓存到ServiceClassHolder中
3.生成Invoker
4.从Invoker得到exporter
5.把exporter加到一个list中
下面重点来看这句话:
Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
proxyFactory.getInvoker(ref, (Class) interfaceClass, local)
proxyFactory的来历
ServiceConfig类中初始化时进行实例化:
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
这里用到dubbo的SPI机制,这个proxyFactory当是com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory类的实例,也就是使用Javassist技术来产生Invoker对象。
ProxyFactory接口有两个方法:
<T> T getProxy(Invoker<T> invoker) throws RpcException; //供消费者使用
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; //服务提供者使用
JavassistProxyFactory实例的 getInvoker方法分析
可以通过如下源码看出Invoker实例在被调用时是通过其内部的wrapper对象调用proxy对象来完成的
下面分析下这个方法传入的参数:
- 第一个参数proxy传入是的ref变量 ,ref的定义:private T ref; //接口实现类的引用 ,就是接口实现类的Class的实例
- 第二个参数 type 的定义是:private Class<?> interfaceClass; //也就是我们定义服务接口
- 第三个参数url 比较理解,是由之前面的调用方法 doExportUrlsFor1Protocol(protocolConfig, registryURLs) 方法拼接出来的
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper类不能正确处理带$的类名
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,换句话说这里就是返回的Invoker
InjvmProtocol的export方法分析
前置调用
1.Protocol$Adaptive的export方法
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("dubbo")
public interface Protocol {
/**
* Export service for remote invocation: <br>
* 1. Protocol should record request source address after receive a request:
* RpcContext.getContext().setRemoteAddress();<br>
* 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
* export the same URL<br>
* 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
*
* @param <T> Service type
* @param invoker Service invoker
* @return exporter reference for exported service, useful for unexport the service later
* @throws RpcException thrown when error occurs during export the service, for example: port is occupied
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
2.这里面的protocol是QosProtocolWrapper
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
startQosServer(invoker.getUrl());
return protocol.export(invoker);
}
// 这里的protocol是ProtocolListenerWrapper
return protocol.export(invoker);
}
这里的protocol是ProtocolListenerWrapper
3. 这里面的protocol是ProtocolListenerWrapper
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//这里不进,要远程暴露才进
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
//这里的protocol是ProtocolFilterWrapper
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
这里的protocol是ProtocolFilterWrapper
4.实际上调用的impl是ProtocolFilterWrapper
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
//这里的protocol是InjvmProtocol
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
5. 这里的protocol是InjvmProtocol
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
这里就是根据Invoker返回Exporter
远程暴露
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
1.调用proxyFactory的getInvoker方法
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
/**
* ProxyFactory. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("javassist")
public interface ProxyFactory {
/**
* create invoker.
*
* @param <T>
* @param proxy
* @param type
* @param url
* @return invoker
*/
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
实际上是调用的ExtensionLoader的getExtensionLoader方法
2.ExtensionLoader的getExtensionLoader方法
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
这里返回的loader是com.alibaba.dubbo.common.extension.ExtensionLoader[com.alibaba.dubbo.rpc.ProxyFactory]
3.然后调用ExtensionLoader的getExtension方法
入参name=javassist
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
这里返回的instance是StubProxyFactoryWrapper
4.StubProxyFactoryWrapper.getInvoker
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
return proxyFactory.getInvoker(proxy, type, url);
}
这里的proxyFactory是JavassistProxyFactory
5.JavassistProxyFactory
@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);
}
};
}
6.Protocol调用
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("dubbo")
public interface Protocol {
/**
* Export service for remote invocation: <br>
* 1. Protocol should record request source address after receive a request:
* RpcContext.getContext().setRemoteAddress();<br>
* 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
* export the same URL<br>
* 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
*
* @param <T> Service type
* @param invoker Service invoker
* @return exporter reference for exported service, useful for unexport the service later
* @throws RpcException thrown when error occurs during export the service, for example: port is occupied
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
}
7.这里又会调用ExtensionLoader.getExtensionLoader,入参type=interface com.alibaba.dubbo.rpc.Protocol
返回的loader=com.alibaba.dubbo.common.extension.ExtensionLoader[com.alibaba.dubbo.rpc.Protocol]
8.这里又会调用ExtensionLoader.getExtension,入参name=registry
返回的instance是QosProtocolWrapper
9.QosProtocolWrapper.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
//这次debug是走的这个里面
startQosServer(invoker.getUrl());
return protocol.export(invoker);
}
//本地暴露的时候debug是走的这里
return protocol.export(invoker);
}
QosProtocolWrapper.startQosServer方法
private void startQosServer(URL url) {
try {
boolean qosEnable = url.getParameter(QOS_ENABLE,true);
//debug的时候这个没进
if (!qosEnable) {
logger.info("qos won't be started because it is disabled. " +
"Please check dubbo.application.qos.enable is configured either in system property, " +
"dubbo.properties or XML/spring boot configuration.");
return;
}
//debug的时候这个没进
if (!hasStarted.compareAndSet(false, true)) {
return;
}
int port = url.getParameter(QOS_PORT, DEFAULT_PORT);
boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP,"false"));
Server server = com.alibaba.dubbo.qos.server.Server.getInstance();
server.setPort(port);
server.setAcceptForeignIp(acceptForeignIp);
server.start();
} catch (Throwable throwable) {
logger.warn("Fail to start qos server: ", throwable);
}
}
这里面主要就是server.start方法,跟进去可以看到是开始QOS服务并且绑定端口,这个是用的netty.
强调是开始qos服务,既是dubbo的在线运维功能。
Server类
public void start() throws Throwable {
if (!hasStarted.compareAndSet(false, true)) {
return;
}
boss = new NioEventLoopGroup(0, new DefaultThreadFactory("qos-boss", true));
worker = new NioEventLoopGroup(0, new DefaultThreadFactory("qos-worker", true));
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker);
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new QosProcessHandler(welcome, acceptForeignIp));
}
});
try {
serverBootstrap.bind(port).sync();
logger.info("qos-server bind localhost:" + port);
} catch (Throwable throwable) {
logger.error("qos-server can not bind localhost:" + port, throwable);
throw throwable;
}
}
这里不细讲这个,后面会专门有一篇来讲dubbo的通讯底层跟netty相关的地方
10.ProtocolListenerWrapper.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
//从这里返回,这里的protocol是ProtocolFilterWrapper
return protocol.export(invoker);
}
return new ListenerExporterWrapper<T>(protocol.export(invoker),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
.getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
}
这里的protocol是ProtocolFilterWrapper
11.ProtocolFilterWrapper.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
//从这里就返回了,这里的protocol是RegistryProtocol
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
这里的protocol是RegistryProtocol
12.RegistryProtocol.export
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
// 暴露invoker,就是服务暴露
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
//zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-annotation-provider&dubbo=2.0.2&export=dubbo%3A%2F%2FXXX.XXX.XXX.XXX%3A20880%2Forg.apache.dubbo.samples.api.client.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-annotation-provider%26bean.name%3DServiceBean%3Aorg.apache.dubbo.samples.api.client.HelloService%26bind.ip%3DXXX.XXX.XXX.XXX%26bind.port%3D20880%26default.timeout%3D1000%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.api.client.HelloService%26methods%3DsayHello%26pid%3D392%26side%3Dprovider%26timestamp%3D1603694032387&pid=392×tamp=1603694031591
URL registryUrl = getRegistryUrl(originInvoker);
//registry provider
// 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
// 获取已注册的服务提供者 URL,比如:
// dubbo://XXX.XXX.XXX.XXX:20880/org.apache.dubbo.samples.api.client.HelloService?anyhost=true&application=dubbo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService&default.timeout=1000&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.api.client.HelloService&methods=sayHello&pid=392&side=provider×tamp=1603694032387
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);
//to judge to delay publish whether or not
// 获取 register 参数 来判断是否延迟发布
boolean register = registeredProviderUrl.getParameter("register", true);
// 向服务提供者与消费者注册表中注册服务提供者
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);
// 根据 register 的值决定是否注册服务
if (register) {
// 向注册中心注册服务
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// 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.
// 获取订阅 URL,比如:
//provider://XXX.XXX.XXX.XXX:20880/org.apache.dubbo.samples.api.client.HelloService?anyhost=true&application=dubbo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService&category=configurators&check=false&default.timeout=1000&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.api.client.HelloService&methods=sayHello&pid=392&side=provider×tamp=1603694032387
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
// 创建监听器
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 向注册中心进行订阅 override 数据
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
// 创建并返回 DestroyableExporter
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}
做了几件事
1.暴露invoker,就是服务暴露,doLocalExport方法里面调用了DubboProtocol.export,生成并返回exporter
2.获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-annotation-provider&dubbo=2.0.2&export=dubbo%3A%2F%2FXXX.XXX.XXX.XXX%3A20880%2Forg.apache.dubbo.samples.api.client.HelloService%3Fanyhost%3Dtrue%26application
%3Ddubbo-annotation-provider%26bean.name%3DServiceBean%3Aorg.apache.dubbo.samples.api.client.HelloService%26bind.ip%3DXXX.XXX.XXX.XXX%26bind.port
%3D20880%26default.timeout%3D1000%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.api.client.HelloService%26methods
%3DsayHello%26pid%3D392%26side%3Dprovider%26timestamp%3D1603694032387&pid=392×tamp=1603694031591
3.根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
4.获取已注册的服务提供者 URL,
比如: dubbo://XXX.XXX.XXX.XXX:20880/org.apache.dubbo.samples.api.client.HelloService?anyhost=true&application=dubbo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService&default.timeout=1000
&dubbo=2.0.2&generic=false&interface=org.apache.dubbo.samples.api.client.HelloService&methods=sayHello&pid=392&side=provider×tamp=1603694032387
5.获取 register 参数
6.向服务提供者与消费者注册表中注册服务提供者
7.根据 register 的值决定是否注册服务
8.获取订阅 URL,
比如: provider://XXX.XXX.XXX.XXX:20880/org.apache.dubbo.samples.api.client.HelloService?anyhost=true
&application=dubbo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.client.HelloService
&category=configurators&check=false&default.timeout=1000&dubbo=2.0.2&generic=false
&interface=org.apache.dubbo.samples.api.client.HelloService&methods=sayHello&pid=392&side=provider×tamp=1603694032387
9.创建监听器
10.向注册中心进行订阅 override 数据
11.创建并返回 DestroyableExporter
13.DubboProtocol.export
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
// 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:
// org.apache.dubbo.samples.api.client.HelloService:20880
String key = serviceKey(url);
// 创建 DubboExporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 将 <key, exporter> 键值对放入缓存中
exporterMap.put(key, exporter);
//export an stub service for dispatching event
// 本地存根相关代码
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
//debug的时候这里面没走
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
// 启动服务器
openServer(url);
// 优化序列化
optimizeSerialization(url);
return exporter;
}
做了几件事
1.获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。
比如:org.apache.dubbo.samples.api.client.HelloService:20880
2.创建 DubboExporter
3.将 <key, exporter> 键值对放入缓存中
4.做本地存根。debug的时候这里的if里面没走
5.openServer启动服务器
6.optimizeSerialization优化序列化
深入跟一下openServer这个方法
DubboProtocol.openServer
private void openServer(URL url) {
// find server.
// 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
String key = url.getAddress();
//client can export a service which's only for server to invoke
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
// 访问缓存
ExchangeServer server = serverMap.get(key);
if (server == null) {
// 创建服务器实例
serverMap.put(key, createServer(url));
} else {
// server supports reset, use together with override
// 服务器已创建,则根据 url 中的配置重置服务器
server.reset(url);
}
}
}
主要做了一件事,就是看服务实例ExchangeServer是否创建,如果没创建则创建,如果创建则根据RUL中的配置重置
在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置
DubboProtocol.createServer方法
private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// 添加心跳检测配置到 url 中
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 获取 server 参数,默认为 netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
// 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
// 添加编码解码器参数
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// 创建 ExchangeServer
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
// 获取 client 参数,可指定 netty,mina
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
// 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
// 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,
// 是否包含 client 所表示的 Transporter,若不包含,则抛出异常
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}
return server;
}
做了几件事:
1.检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。
2.Exchangers.bind(url, requestHandler);创建服务器实例。
3.检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。
再跟进去看创建服务器实例的方法
Exchangers.bind
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 获取 Exchanger,默认为 HeaderExchanger。
// 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
return getExchanger(url).bind(url, handler);
}
获取 Exchanger,默认为 HeaderExchanger。然后调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
HeaderExchanger.bind
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
HeaderExchangeServer的构造函数 里面有个开始心跳的方法startHeartbeatTimer();
Transporters.bind方法
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
handler = new ChannelHandlerDispatcher(handlers);
}
// 获取自适应 Transporter 实例,并调用实例方法
return getTransporter().bind(url, handler);
}
getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 TransporterAdaptive,也就是自适应拓展类。
TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。
跟入NettyTransporter
NettyTransporter.bind
@Override
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
NettyServer的构造函数
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
其实就是调用父类的构造函数
AbstractServer的构造函数
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
localAddress = getUrl().toInetSocketAddress();
// 获取 ip 和端口
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
// 设置 ip 为 0.0.0.0
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
// 获取最大可接受连接数
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
// 调用模板方法 doOpen 启动服务器
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
这里主要就是
1.获取IP和端口
2.设置IP为0.0.0.0
3.获取最大可连接数
4.获取空闲超时时间
5.调用模板方法doOpen启动服务
NettyServer.doOpen方法
@Override
protected void doOpen() throws Throwable {
// 创建 ServerBootstrap
bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
new DefaultThreadFactory("NettyServerWorker", true));
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("handler", nettyServerHandler);
}
});
// bind
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
}
这里就是真正的启动了服务,在监听端口等待被人调用