不管是通过xml方式,还是注解方式,还是API方式都会生成对应的XXXConfig
类,然后解析这些XXXConfig类生成URL的参数信息,例如<dubbo:application> -> ApplicationConfig,<dubbo:registry> -> RegistryConfig
。
如果我们查看在zk中注册的服务提供者信息(服务消费者类似)
,可能看到如下的信息:
[zk: localhost:2181(CONNECTED) 8] ls /dubbo/dongshi.daddy.api.UserRpcService/providers
[dubbo://192.168.10.119:20881/dongshi.daddy.api.UserRpcService?anyhost=true&application=user-service-provider11191458&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=dongshi.daddy.api.UserRpcService&methods=get&pid=19844&release=2.7.4.1&side=provider&threads=101×tamp=1637462018314]
比如其中的端口号20881
,线程数threads=101
就是通过类ProtocolConfig
来配置的,interface=dongshi.daddy.api.UserRpcService
就是通过ServiceConfig
来配置的,本文我们要分析的也正是dubbo对于这些配置的相关源码,推荐通过这篇文章 来感受一下。
我们想要使用dubbo,不管是服务提供者还是服务消费者,为了能够定制和表达自己的信息,或者是自己要使用的信息,需要进行一系列的配置,比如服务提供者要设置自己的应用信息,协议信息,连接注册中心的信息等,dubbo针对此提供了配置相关API,其中顶层的是一个抽象类org.apache.dubbo.config.AbstractConfig
,在这个抽象类中主要定义了一些公共的属性,公共的工具类等,我们在后面的部分来继续看下这些内容。
org.apache.dubbo.config.AbstractConfig
是dubbo配置相关的顶层抽象类,在这篇文章 中我使用了相关的API来进行服务提供者和服务消费者的定义,比如如下定义服务提供者的代码:
public class ProviderApplication {
public static void main(String[] args) {
// 当前应用配置
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("user-service-provider11191458");
// 连接注册中心配置
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
// 服务提供者协议配置
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
protocolConfig.setThreads(100);
// 暴露服务
ServiceConfig<UserRpcService> serviceConfig = new ServiceConfig<>();
serviceConfig.setApplication(applicationConfig);
serviceConfig.setRegistry(registryConfig); // 设置注册地址
serviceConfig.setProtocol(protocolConfig); // 设置协议
serviceConfig.setInterface(UserRpcService.class);
serviceConfig.setRef(new UserRpcServiceImpl()); // 设置具体实现类
serviceConfig.export(); // 完成暴露
new Scanner(System.in).next();
}
}
其中的类ApplicationConfig
是AbstractConfig的子类,全限定名是org.apache.dubbo.config.ApplicationConfig
,我们以代码registryConfig.setProtocol("zookeeper");
看下是如何调用到AbstractConfig的工具类方法的,首先执行方法如下:
// org.apache.dubbo.config.ApplicationConfig#setName
public void setName(String name) {
// 2021-11-21 16:06:24
checkName(NAME, name);
this.name = name;
// 2021-11-21 16:07:02
if (StringUtils.isEmpty(id)) {
id = name;
}
}
2021-11-21 16:06:24
处调用的就是父类AbstractConfig的方法了,源码如下:
// org.apache.dubbo.config.AbstractConfig#checkName
protected static void checkName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_NAME);
}
// org.apache.dubbo.config.AbstractConfig#checkProperty
protected static void checkProperty(String property, String value, int maxlength, Pattern pattern) {
// 允许给空
if (StringUtils.isEmpty(value)) {
return;
}
// 判断长度是否符合要求
if (value.length() > maxlength) {
throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" is longer than " + maxlength);
}
// 判断内容是否符合要求
if (pattern != null) {
Matcher matcher = pattern.matcher(value);
if (!matcher.matches()) {
throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" contains illegal " +
"character, only digit, letter, '-', '_' or '.' is legal.");
}
}
}
修改代码registryConfig.setProtocol("zookeeper");->registryConfig.setProtocol("zookeeper***");
,因为不允许出现*
,则会抛出如下异常:
Exception in thread "main" java.lang.IllegalStateException: Invalid protocol="zookeeper**" contains illegal character, only digit, letter, '-', '_' or '.' is legal.
at org.apache.dubbo.config.AbstractConfig.checkProperty(AbstractConfig.java:381)
at org.apache.dubbo.config.AbstractConfig.checkName(AbstractConfig.java:339)
at org.apache.dubbo.config.RegistryConfig.setProtocol(RegistryConfig.java:169)
at dongshi.daddy.provider.ProviderApplication.main(ProviderApplication.java:23)
2021-11-21 16:07:02
处是设置id,这也是在AbstractConfig中定义的属性,用来作为配置的唯一标识,定义如下:
/**
* The config id
*/
protected String id;
接下来我们来看下方法org.apache.dubbo.config.AbstractConfig#appendParameters
,在看这个方法之前先看下3:URL
,4:Parameter
:
// org.apache.dubbo.config.AbstractConfig#appendParameters(java.util.Map<java.lang.String,java.lang.String>, java.lang.Object, java.lang.String)
// parameters,url的参数信息,最终封装到org.apache.dubbo.common.URL#parameters
// config:用于解析url参数的配置类
// prefix:前缀,配置项添加到参数中的键的前缀
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
// 配置类为null直接return
if (config == null) {
return;
}
// 获取所有的方法
Method[] methods = config.getClass().getMethods();
// 依此处理每个方法,获取url的参数信息
for (Method method : methods) {
try {
String name = method.getName();
if (MethodUtils.isGetter(method)) { // 普通的get方法
// 尝试获取@Parameter注解
Parameter parameter = method.getAnnotation(Parameter.class);
// 当返回类型是Object,配置了@Paramter注解,但是注解的excluded属性为true,则continue,即过滤掉该方法
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
continue;
}
String key;
// 获取key,配置了@Parameter注解则使用key属性,否则使用属性值
if (parameter != null && parameter.key().length() > 0) {
key = parameter.key();
} else {
key = calculatePropertyFromGetter(name);
}
// 反射调用get方法获取值
Object value = method.invoke(config);
String str = String.valueOf(value).trim();
if (value != null && str.length() > 0) {
// 需要URL编码
if (parameter != null && parameter.escaped()) {
str = URL.encode(str);
}
// 需要拼接前缀
if (parameter != null && parameter.append()) {
String pre = parameters.get(DEFAULT_KEY + "." + key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
pre = parameters.get(key);
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
}
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
parameters.put(key, str);
} else if (parameter != null && parameter.required()) { // Paramter注解中设置了强制需要,但是没有返回值,则抛出java.lang.IllegalStateException
throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
}
} else if (isParametersGetter(method)) { // 如果是getParameter方法,这种方式是直接通过返回map的方式来获取参数,即批量方式
Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
parameters.putAll(convert(map, prefix));
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
与该方法类似的还有一个org.apache.dubbo.config.AbstractConfig#appendAttributes
是用来获取方法上设置了Paramter(attributes=true)
的方法,源码如下:
@Deprecated // 废弃。慎用!
protected static void appendAttributes(Map<String, Object> parameters, Object config, String prefix) {
if (config == null) {
return;
}
Method[] methods = config.getClass().getMethods();
// 循环处理
for (Method method : methods) {
try {
// 获取@org.apache.dubbo.config.support.Parameter注解
Parameter parameter = method.getAnnotation(Parameter.class);
// 配置了,并且attribute属性值为true才继续
if (parameter == null || !parameter.attribute()) {
continue;
}
// 获取方法名称
String name = method.getName();
// 获取键和值
if (MethodUtils.isGetter(method)) {
String key;
if (parameter.key().length() > 0) {
key = parameter.key();
} else {
key = calculateAttributeFromGetter(name);
}
Object value = method.invoke(config);
if (value != null) {
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
parameters.put(key, value);
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
具体的配置类和其对应的xml如下:
ApplicationConfig:<dubbo:application>配置应用级别的信息,如名称,所有者等。
RegistryConfig:<dubbo:registry>配置注册中心的信息,如配置zk,nacos的地址。
ModuleConfig:<dubbo:module>。
MonitorConfig:<dubbo:monitor>。
ArgumentConfig:<dubbo:argument>。
计算机中URL形式是协议://ip:port/path?key1=val1&key2=val2&key3=val3
以及协议://username:password/path?key1=val1&key2=val2&key3=val3
,org.apache.dubbo.common.URL
类就是是用来封装dubbo中可能使用到的url格式信息的类,如zookeeper://127.0.0.1:2181/ConfigCenterConfig?check=true&config-file=dubbo.properties
,
dubbo://192.168.10.119:20881/dongshi.daddy.api.UserRpcService?anyhost=true&application=user-service-provider11191458&deprecated=false
,
consumer://192.168.10.119/dongshi.daddy.api.UserRpcService?application=user-service-consumer37
,在整个网络世界可能的格式如下:
常规的:
http://www.facebook.com/friends?param1=value1&param2=value2
http://username:password@10.20.130.230:8080/list?version=1.0.0
ftp://username:password@192.168.1.7:21/1/read.txt
registry://192.168.1.7:9090/org.apache.dubbo.service1?param1=value1&param2=value2
非常规的:
192.168.1.3:20880->这种情况,url协议=null,url host=192.168.1.3, port = 20880, url path = null
file:///home/user1/router.js?type=script->这种情况 url协议=file,url host=null,url path=/home/user1/router.js
file://home/user1/router.js?type=script->这种情况, url protocol = file, url host = home, url path = user1/router.js
file:///D:/1/router.js?type=script->这种情况, url protocol = file, url host = null, url path = D:/1/router.js
file:/D:/1/router.js?type=script->这种情况同上一种情况,file:///D:/1/router.js?type=script
/home/user1/router.js?type=script->这种情况, url protocol = null, url host = null, url path = home/user1/router.js
home/user1/router.js?type=script->这种情况, url protocol = null, url host = home, url path = user1/router.js
URL类源码如下:
// org.apache.dubbo.common.URL
public /*final**/
class URL implements Serializable {
private static final long serialVersionUID = -1985165475234910535L;
// url协议
private final String protocol;
// url用户名
private final String username;
// url密码
private final String password;
// url中的host,默认是注册中心的地址,如zk的ip地址
private final String host;
// url中的port,默认是注册中心的端口,如zk的默认端口2181
private final int port;
// url路径
private final String path;
// url参数
private final Map<String, String> parameters;
// ==== 提高效率相关缓存参数开始 ===== //
private volatile transient Map<String, Number> numbers;
private volatile transient Map<String, URL> urls;
private volatile transient String ip;
private volatile transient String full;
private volatile transient String identity;
private volatile transient String parameter;
private volatile transient String string;
// ==== 提高效率相关缓存参数结束 ===== //
}
dubbo会通过类的getXxx方法来生成url参数的key和value,默认key就是属性本身,value就是方法的返回值,为了可自定义,定义了注解org.apache.dubbo.config.support.Parameter
用在方法上,源码如下:
// org.apache.dubbo.config.support.Parameter
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Parameter {
// 设置参数键
String key() default "";
// 是否必须
boolean required() default false;
// 是否排除不作为参数的一部分
boolean excluded() default false;
// 是否转义
boolean escaped() default false;
// 属性
boolean attribute() default false;
// 是否拼接,如拼接前缀
boolean append() default false;
boolean useKeyAsProperty() default true;
}