1:写在前面
在这篇文章 中我们分析了服务提供者配置的相关源码,本文一起来看下服务消费者配置相关的源码,还是看一个通过API 方式配置服务消费者的实例代码:
public class ConsumerWithApiConfigMain {
public static void main(String[] args) {
// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("dongshidaddy-consumer");
application.setOwner("dongshidaddy");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://192.168.10.119:2181");
// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
// 引用远程服务
ReferenceConfig<ProviderService> reference = new ReferenceConfig<ProviderService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
reference.setApplication(application);
reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
reference.setInterface(ProviderService.class);
// 和本地bean一样使用xxxService
ProviderService providerService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
String s = providerService.SayHello("hello dubbo! I am dongshidadddy!");
System.out.println(s);
}
}
下面我们来一起看下其中使用到的配置类。
2:ConsumerConfig
类全限定名是com.alibaba.dubbo.config.ConsumerConfig
,主要源码如下:
public class ConsumerConfig extends AbstractReferenceConfig {
private static final long serialVersionUID = 2827274711143680600L;
private Boolean isDefault;
// 客户端的类型,netty,mina等
private String client;
// 消费者线程池类型,如cached,fixed,limit,eager等
private String threadpool;
// 消费者核心线程池大小
private Integer corethreads;
// 消费者线程池大小
private Integer threads;
// 消费者任务队列大小
private Integer queues;
}
对应的标签是dubbo:consumer ,可能的配置如<dubbo:consumer client="netty" corethreads="20">
。
3:ReferenceConfig
类全限定名是com.alibaba.dubbo.config.ReferenceConfig
,源码如下:
public class ReferenceConfig<T> extends AbstractReferenceConfig {
private static final long serialVersionUID = -5864351140409987595L;
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private final List<URL> urls = new ArrayList<URL>();
// 服务接口名称
private String interfaceName;
private Class<?> interfaceClass;
// 客户端类型
private String client;
// url for peer-to-peer invocation
private String url;
// method configs
private List<MethodConfig> methods;
// <dubbo:consumer>
private ConsumerConfig consumer;
private String protocol;
// interface proxy reference
private transient volatile T ref;
private transient volatile Invoker<?> invoker;
private transient volatile boolean initialized;
private transient volatile boolean destroyed;
}
通过开始的实例代码可以看到最终获取服务提供者的服务类是通过方法ReferenceConfig#get
,主要做了以下几件事:
1:进一步初始化和校验ReferenceConfig。
2:使用ReferenceConfig,生成DUBBO URL数组。
3:使用DUBBO URL应用服务。
get方法源码如下:
// com.alibaba.dubbo.config.ReferenceConfig.get
class FakeCls {
public synchronized T get() {
// 是否已经销毁,如果是已经销毁的话,抛出java.lang.IllegalStateException
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// private transient volatile T ref; 还没有初始化才进行初始化
if (ref == null) {
// 2021-12-02 10:31:11
init();
}
return ref;
}
}
2021-12-02 10:31:11
处是进行服务代理类的初始化,详细参考3.1:init
。
3.1:init
源码如下:
// com.alibaba.dubbo.config.ReferenceConfig.init
class FakeCs {
private void init() {
// 已经初始化
if (initialized) {
return;
}
// 将初始化的标记设置为true,防止重复初始化
initialized = true;
// 没有设置服务接口,即<dubbo:reference interface="">的情况,抛出java.lang.IllegalStateException
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// 添加环境变量配置的外部属性到ConsumerConfig中,即<dubbo:consumer>
checkDefault();
// 添加环境变量配置的外部属性到PreferenceConfig,即<dubbo:reference>
appendProperties(this);
// 泛化接口,如是是在<dubbo:reference>中没有配置,且配置了<dubbo:consumer>则从<dubbo:consumer>中获取
// 默认为false,即不是泛化接口
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
// 如果是泛化接口,则设置接口类型为GenericService.class,判断如下:
/*
public static boolean isGeneric(String generic) {
return generic != null
&& !"".equals(generic)
&& (Constants.GENERIC_SERIALIZATION_DEFAULT.equalsIgnoreCase(generic) // public static final String GENERIC_SERIALIZATION_DEFAULT = "true";
|| Constants.GENERIC_SERIALIZATION_NATIVE_JAVA.equalsIgnoreCase(generic) // public static final String GENERIC_SERIALIZATION_NATIVE_JAVA = "nativejava";
|| Constants.GENERIC_SERIALIZATION_BEAN.equalsIgnoreCase(generic)); // public static final String GENERIC_SERIALIZATION_BEAN = "bean";
}
*/
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
// 普通服务接口,根据接口名称获取class对象
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 检测<dubbo:reference>中配置的<dubbo:method>是否在接口中真的存在
checkInterfaceAndMethods(interfaceClass, methods);
}
// ***直连提供者逻辑开始*** //
// 这种是配置 java -D com.jh.xxx.XxxService=dubbo://192.168.10.119:20880 xxx.jar
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
// 没有配置java -D com.jh.xxx.XxxService=dubbo://192.168.10.119:20880 xxx.jar
if (resolve == null || resolve.length() == 0) {
// 获取配置文件,如System.setProperty("dubbo.resolve.file", "/etc/app/dubbop2p.properties"):
/*
/etc/app/dubbop2p.properties示例内容:
com.jh.xxx.XxxService=dubbo://192.168.10.119:20880
com.jh.xxx.YyyService=dubbo://192.168.10.119:20880
*/
resolveFile = System.getProperty("dubbo.resolve.file");
// 如果是没有设置System.setProperty("dubbo.resolve.file", "/etc/app/dubbop2p.properties");
if (resolveFile == null || resolveFile.length() == 0) {
// 获取user.home环境变量下的dubbo-resolve.properties文件,如C:\Users\Administrator\dubbo-resolve.properties
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
resolveFile = userResolveFile.getAbsolutePath();
}
}
// 在System.getProperty("user.home")下有dubbo-resolve.properties
if (resolveFile != null && resolveFile.length() > 0) {
// 将System.getProperty("user.home")/dubbo-resolve.properties文件内容加载到java.util.Properties中
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
// 获取配置的dubbo url
resolve = properties.getProperty(interfaceName);
}
}
// 主要是给出警告日志,和警告提示
if (resolve != null && resolve.length() > 0) {
url = resolve;
if (logger.isWarnEnabled()) {
// resolveFile != null说明是通过System.getProperty("user.home")/dubbo-resolve.properties文件获取的dubbo url,给出相应的日志提示
// 否则是通过java -Dcom.jh.xxx.XxxService=dubbo://192.168.10.119:20880 xxx.jar 获取的dubbo url,给出相应的日志提示
// 因为到底dubbo url是如何获取的直接影响了整个消费者的调用,所以使用了warn级别的日志来进行打印
if (resolveFile != null) {
logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
} else {
logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
}
}
}
// ***直连提供者逻辑结束*** //
// ***获取各种config开始*** //
if (consumer != null) {
if (application == null) {
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
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();
}
}
// ***获取各种config结束*** //
// 追加系统属性到application配置中
checkApplication();
// 检查stub
checkStub(interfaceClass);
// 检查mock
checkMock(interfaceClass);
// 生成consumer:// url的参数的字典
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
// 设置为消费者端
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
// 设置jvm进程号
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 不是泛化接口,则需要设置方法信息
if (!isGeneric()) {
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)), ","));
}
}
// 设置接口信息
map.put(Constants.INTERFACE_KEY, interfaceName);
// 追加application配置信息到参数字典中
appendParameters(map, application);
// 追加module配置信息到参数字典中
appendParameters(map, module);
// 追加consumer配置信息到参数字典中
appendParameters(map, consumer, Constants.DEFAULT_KEY);
// 追加自己,即reference配置到参数字典中
appendParameters(map, this);
String prefix = StringUtils.getServiceKey(map);
// 处理<dubbo:method>的配置信息
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");
}
}
// 将配置了@Parameter(attribute=true)的参数添加到attributes中
appendAttributes(attributes, method, prefix + "." + method.getName());
// 2021-12-03 13:47:05
checkAndConvertImplicitConfig(method, map, attributes);
}
}
// 使用DUBBO_IP_TO_REGISTRY作为注册到注册中心的地址
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
// 没有配置,则获取本机网卡IP地址
if (hostToRegistry == null || hostToRegistry.length() == 0) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//attributes are stored by system context.
StaticContext.getSystemContext().putAll(attributes);
// 应用服务,暂时忽略
// ...
}
}
2021-12-03 13:47:05
处是检查事件通知 配置是否正确,源码如下:
// checkAndConvertImplicitConfig
class FakeCls {
private static void checkAndConvertImplicitConfig(MethodConfig method, Map<String, String> map, Map<Object, Object> attributes) {
// 是否设置了<dubbo:method return="false" async="true">即不需要返回值,因为这种情况直接异步返回null,而不管后续执行情况,所以如果配置了onreutn,onthrow等和执行结果有关系的内容则异常
// 详细参考:https://dubbo.apache.org/zh/docs/references/xml/dubbo-method/ 搜索”<methodName>.return“
if (Boolean.FALSE.equals(method.isReturn()) && (method.getOnreturn() != null || method.getOnthrow() != null)) {
throw new IllegalStateException("method config error : return attribute must be set true when onreturn or onthrow has been setted.");
}
// 转换onreturn为对应的java.lang.reflect.Method
String onReturnMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_RETURN_METHOD_KEY);
Object onReturnMethod = attributes.get(onReturnMethodKey);
if (onReturnMethod instanceof String) {
attributes.put(onReturnMethodKey, getMethodByName(method.getOnreturn().getClass(), onReturnMethod.toString()));
}
// 转换onthrow为对应的java.lang.reflect.Method
String onThrowMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_THROW_METHOD_KEY);
Object onThrowMethod = attributes.get(onThrowMethodKey);
if (onThrowMethod instanceof String) {
attributes.put(onThrowMethodKey, getMethodByName(method.getOnthrow().getClass(), onThrowMethod.toString()));
}
// 转换onInvoke为对应的java.lang.reflect.Method
String onInvokeMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_INVOKE_METHOD_KEY);
Object onInvokeMethod = attributes.get(onInvokeMethodKey);
if (onInvokeMethod instanceof String) {
attributes.put(onInvokeMethodKey, getMethodByName(method.getOninvoke().getClass(), onInvokeMethod.toString()));
}
}
}