目录:
-
AbstractConfig-结构图
-
静态属性-属性约束
-
静态方法-约束校验
-
AbstractConfig#appendAttributes()–事件通知
-
AbstractConfig#appendParameters(parameters,config,prefix)–xml配置
-
AbstractConfig#appendAnnotation()–注解配置
-
AbstractConfig#appendParameters(config)–属性配置
1.AbstractConfig-结构图
说明:
com.alibaba.dubbo.config.AbstractConfig
,抽象配置类,除了ArgumentConfig
,我们可以看到所有的配置类都继承该类。
AbstractConfig
主要提供配置解析与校验相关的工具方法
因为API可以直接进行setXXX(config)对象的配置,AbstractConfig
也就不适用于api配置了。
2.静态属性-属性约束
// ========== 属性值的格式校验,参见本类的 `#checkXXX` 方法 BEGIN ==========
private static final int MAX_LENGTH = 200;
private static final int MAX_PATH_LENGTH = 200;
private static final Pattern PATTERN_NAME = Pattern.compile("[\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_MULTI_NAME = Pattern.compile("[,\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_METHOD_NAME = Pattern.compile("[a-zA-Z][0-9a-zA-Z]*");
private static final Pattern PATTERN_PATH = Pattern.compile("[/\\-$._0-9a-zA-Z]+");
private static final Pattern PATTERN_NAME_HAS_SYMBOL = Pattern.compile("[:*,/\\-._0-9a-zA-Z]+");
private static final Pattern PATTERN_KEY = Pattern.compile("[*,\\-._0-9a-zA-Z]+");
// ========== 属性值的格式校验,参见本类的 `#checkXXX` 方法 END ==========
3.静态方法-约束校验
protected static void checkLength(String property, String value) {
checkProperty(property, value, MAX_LENGTH, null);
}
protected static void checkPathLength(String property, String value) {
checkProperty(property, value, MAX_PATH_LENGTH, null);
}
protected static void checkName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_NAME);
}
protected static void checkNameHasSymbol(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_NAME_HAS_SYMBOL);
}
protected static void checkKey(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_KEY);
}
protected static void checkMultiName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_MULTI_NAME);
}
protected static void checkPathName(String property, String value) {
checkProperty(property, value, MAX_PATH_LENGTH, PATTERN_PATH);
}
protected static void checkMethodName(String property, String value) {
checkProperty(property, value, MAX_LENGTH, PATTERN_METHOD_NAME);
}
protected static void checkParameterName(Map<String, String> parameters) {
if (parameters == null || parameters.size() == 0) {
return;
}
for (Map.Entry<String, String> entry : parameters.entrySet()) {
checkNameHasSymbol(entry.getKey(), entry.getValue());
}
}
protected static void checkProperty(String property, String value, int maxlength, Pattern pattern) {
if (value == null || value.length() == 0) {
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.");
}
}
}
3.appendAttributes(parameters,config, prefix)-事件通知
appendAttributes(parameters, config, prefix)
方法,将 @Parameter(attribute = true)
配置对象的属性,添加到参数集合
注意:该方法主要用于事件通知《dubbo文档-示例-事件通知》
3.1.提供单元测试:
//测试:
@Test
public void testAppendAttributes1() throws Exception {
Map<Object, Object> parameters = new HashMap<Object, Object>();
AbstractConfig.appendAttributes(parameters, new AttributeConfig('l', true, (byte) 0x01), "prefix");
TestCase.assertEquals('l', parameters.get("prefix.let"));
TestCase.assertEquals(true, parameters.get("prefix.activate"));
TestCase.assertFalse(parameters.containsKey("prefix.flag"));
}
//准备数据:
private static class AttributeConfig {
private char letter;
private boolean activate;
private byte flag;
public AttributeConfig(char letter, boolean activate, byte flag) {
this.letter = letter;
this.activate = activate;
this.flag = flag;
}
//因为attribute=false 所以Letter参数不会被拼接
@Parameter(attribute = false)
public char getLetter() {
return letter;
}
//没有注释也不会被拼接
public void setLetter(char letter) {
this.letter = letter;
}
//形式为:act=true
@Parameter(attribute = true, key="act")
public boolean isActivate() {
return activate;
}
//省略之后的代码。。。。
测试结果:
console.log:act=true
3.2.[源码]appendAttributes()
/**
*@Description: 将 @Parameter(attribute = true) 配置对象的属性,添加到参数集合
* @param parameters 参数集合。实际上,该集合会用于 URL.parameters
* @param config 配置对象
* @param prefix 属性前缀。用于配置项添加到 parameters 中时的前缀。
*/
protected static void appendAttributes(Map<Object, Object> parameters, Object config, String prefix) {
if (config == null) {
return;
}
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
if ((name.startsWith("get") || name.startsWith("is"))
&& !"getClass".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& isPrimitive(method.getReturnType())) {// 方法为获取基本类型,public 的 getting 方法。
/**
* 获取注解@parameter=true的值
*/
Parameter parameter = method.getAnnotation(Parameter.class);
if (parameter == null || !parameter.attribute()) {//!parameter.attribute()判断@Parameter(attribute = ?) ?:是不是true
continue;
}
// 获得属性名
String key;
if (parameter.key().length() > 0) {
key = parameter.key();
} else {
int i = name.startsWith("get") ? 3 : 2;
key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1);
}
// 获得属性值,存在则添加到 `parameters` 集合
Object value = method.invoke(config);
if (value != null) {
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
parameters.put(key, value);
System.out.println(key+"="+value);//用于测试结果
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
4.appendParameters(parameters,config, prefix)-xml配置
4.1.提供单元测试:
//测试:
@Test
public void testAppendParameters4() throws Exception {
Map<String, String> parameters = new HashMap<String, String>();
AbstractConfig.appendParameters(parameters, new ParameterConfig(1, "hello/world", 30, "password"));
TestCase.assertEquals("one", parameters.get("key.1"));
TestCase.assertEquals("two", parameters.get("key.2"));
TestCase.assertEquals("1", parameters.get("num"));
TestCase.assertEquals("hello%2Fworld", parameters.get("naming"));
TestCase.assertEquals("30", parameters.get("age"));
}
//准备测试bean
private static class ParameterConfig {
private int number;
private String name;
private int age;
private String secret;
ParameterConfig(int number, String name, int age, String secret) {
this.number = number;
this.name = name;
this.age = age;
this.secret = secret;
}
@Parameter(key = "num", append = true)
public int getNumber() {
return number;
}
@Parameter(key = "naming", append = true, escaped = true, required = true)
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Parameter(excluded = true)
public String getSecret() {
return secret;
}
public Map getParameters() {
Map<String, String> map = new HashMap<String, String>();
map.put("key.1", "one");
map.put("key-2", "two");
return map;
}
//省略。。。。。。
//测试机结果:
console.log :kv:num 1
kv:naming hello%2Fworld
kv:key.1 one
kv:key.2 two
kv:age 30
4.2.[源码]appendAttributes()
/**
* @SuppressWarnings("unchecked")
* 屏蔽某些编译时的警告信息在强制类型转换的时候编译器会给出警告加上
* * 总之就是屏蔽警告用的
*/
/**
* @description: 将配置对象的属性,添加到参数集合
* @param parameters:参数集合;该集合会用于 URL.parameters
* @param config:配置对象
* @param prefix:属性前缀。用于配置项添加到 parameters 中时的前缀。
*/
@SuppressWarnings("unchecked")
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
if (config == null) {
return;
}
//反射拿到config类的所有方法(其实就是配置项)
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
if ((name.startsWith("get") || name.startsWith("is"))
&& !"getClass".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& isPrimitive(method.getReturnType())) {
Parameter parameter = method.getAnnotation(Parameter.class);
if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
continue;
}
// 获得属性名
int i = name.startsWith("get") ? 3 : 2;
String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), ".");
String key;
if (parameter != null && parameter.key().length() > 0) {
key = parameter.key();
} else {
key = prop;
}
// 获得属性值
Object value = method.invoke(config);
String str = String.valueOf(value).trim();
// URL.encode()转义字符
if (value != null && str.length() > 0) {
if (parameter != null && parameter.escaped()) {
str = URL.encode(str);
}
//拼接,详细说明参见 `Parameter#append()` 方法的说明。
if (parameter != null && parameter.append()) {
String pre = parameters.get(Constants.DEFAULT_KEY + "." + key);// default. 里获取,适用于 ServiceConfig =》ProviderConfig 、ReferenceConfig =》ConsumerConfig 。
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
pre = parameters.get(key);// 通过 `parameters` 属性配置,例如 `AbstractMethodConfig.parameters` 。
if (pre != null && pre.length() > 0) {
str = pre + "," + str;
}
}
if (prefix != null && prefix.length() > 0) {
key = prefix + "." + key;
}
//添加配置项到 parameters
parameters.put(key, str);
System.out.println("kv:" + key + "\t" + str);
//当 `@Parameter.required = true` 时,校验配置项非空。
} else if (parameter != null && parameter.required()) {
throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
}
//方法为 #getParameters()
/**
* getParameters为map等缓存填充数据,可以进行动态拓展配置项,可以拓展出非 Dubbo 内置的逻辑。
*/
} else if ("getParameters".equals(name)
&& Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 0
&& method.getReturnType() == Map.class) {
//通过反射,获得 #getParameters() 的返回值为 map 。
Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
if (map != null && map.size() > 0) {
String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : "");
for (Map.Entry<String, String> entry : map.entrySet()) {
//将 map 添加到 parameters ,kv 格式为 prefix:entry.key entry.value 。
parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue());
System.out.println("kv:"+pre + entry.getKey().replace('-', '.')+"\t"+entry.getValue());
}
}
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
5.appendAnnotation(parameters,config, prefix)-注解配置
5.1.测试
@Test
//通过注解添加属性值
@Config(interfaceClass = Greeting.class, filter = {"f1, f2"}, listener = {"l1, l2"},parameters = {"k1", "v1", "k2", "v2"})
public void appendAnnotation() throws Exception {
//测试注解的好方法!
Config config = getClass().getMethod("appendAnnotation").getAnnotation(Config.class);
AnnotationConfig annotationConfig = new AnnotationConfig();
annotationConfig.appendAnnotation(Config.class, config);
TestCase.assertSame(Greeting.class, annotationConfig.getInterface());
TestCase.assertEquals("f1, f2", annotationConfig.getFilter());
TestCase.assertEquals("l1, l2", annotationConfig.getListener());
TestCase.assertEquals(2, annotationConfig.getParameters().size());
TestCase.assertEquals("v1", annotationConfig.getParameters().get("k1"));
TestCase.assertEquals("v2", annotationConfig.getParameters().get("k2"));
assertThat(annotationConfig.toString(), Matchers.containsString("filter=\"f1, f2\" "));
assertThat(annotationConfig.toString(), Matchers.containsString("listener=\"l1, l2\" "));
}
//测试Object
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Config {
Class<?> interfaceClass() default void.class;
String interfaceName() default "";
String[] filter() default {};
String[] listener() default {};
String[] parameters() default {};
}
private static class AnnotationConfig extends AbstractConfig {
private Class interfaceClass;
private String filter;
private String listener;
private Map<String, String> parameters;
public Class getInterface() {
return interfaceClass;
}
public void setInterface(Class interfaceName) {
this.interfaceClass = interfaceName;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
public String getListener() {
return listener;
}
public void setListener(String listener) {
this.listener = listener;
}
public Map<String, String> getParameters() {
return parameters;
}
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
}
5.2.[源码]appendAnnotation()
/**
* 将注解添加到配置对象中
*/
protected void appendAnnotation(Class<?> annotationClass, Object annotation) {
Method[] methods = annotationClass.getMethods();//反射拿到类的方法
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() != Object.class
&& method.getReturnType() != void.class //过滤返回不为void的方法
&& method.getParameterTypes().length == 0 //获得参数类型数量为O的方法(也就是无参方法)
&& Modifier.isPublic(method.getModifiers()) //过滤public方法
&& !Modifier.isStatic(method.getModifiers())) { //过滤静态方法
try {
//方法名
String property = method.getName();
//两个方法的特殊处理,因为都是描述interface用的
if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
property = "interface";
}
//setXxx
String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
Object value = method.invoke(annotation);
if (value != null && !value.equals(method.getDefaultValue())) {
//方法返回类型(原始类型转为包装类型))
Class<?> parameterType = ReflectUtils.getBoxedClass(method.getReturnType());
//filter和listener是String[]类型的,转为String
if ("filter".equals(property) || "listener".equals(property)) {
parameterType = String.class;
value = StringUtils.join((String[]) value, ",");
//parameters转为键值一一对应的map
} else if ("parameters".equals(property)) { //也有map - parameters的动态拓展
parameterType = Map.class;
value = CollectionUtils.toStringMap((String[]) value);
}
try {
//对当前类的set方法进行注入,即将注解的属性注入当前对象
Method setterMethod = getClass().getMethod(setter, parameterType);
setterMethod.invoke(this, value);
} catch (NoSuchMethodException e) {
// ignore
}
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
}
7.appendProperties(config)-属性配置
7.1.测试
//测试
@Test
public void testAppendProperties1() throws Exception {
try {
System.setProperty("dubbo.properties.i", "1");
System.setProperty("dubbo.properties.c", "c");
System.setProperty("dubbo.properties.b", "2");
System.setProperty("dubbo.properties.d", "3");
System.setProperty("dubbo.properties.f", "4");
System.setProperty("dubbo.properties.l", "5");
System.setProperty("dubbo.properties.s", "6");
System.setProperty("dubbo.properties.str", "dubbo");
System.setProperty("dubbo.properties.bool", "true");
PropertiesConfig config = new PropertiesConfig();
AbstractConfig.appendProperties(config);
TestCase.assertEquals(1, config.getI());
TestCase.assertEquals('c', config.getC());
TestCase.assertEquals((byte) 0x02, config.getB());
TestCase.assertEquals(3d, config.getD());
TestCase.assertEquals(4f, config.getF());
TestCase.assertEquals(5L, config.getL());
TestCase.assertEquals(6, config.getS());
TestCase.assertEquals("dubbo", config.getStr());
TestCase.assertTrue(config.isBool());
} finally {
System.clearProperty("dubbo.properties.i");
System.clearProperty("dubbo.properties.c");
System.clearProperty("dubbo.properties.b");
System.clearProperty("dubbo.properties.d");
System.clearProperty("dubbo.properties.f");
System.clearProperty("dubbo.properties.l");
System.clearProperty("dubbo.properties.s");
System.clearProperty("dubbo.properties.str");
System.clearProperty("dubbo.properties.bool");
}
}
7.2.[源码]appendProperties(config)
/**
* @param config 配置对象
* @Description: 读取启动参数变量和 properties 配置到配置对象。
*/
protected static void appendProperties(AbstractConfig config) { //eg:<dubbo:provider/>
if (config == null) {
return;
}
String prefix = "dubbo." + getTagName(config.getClass()) + ".";//eg:dubbo.provider.
Method[] methods = config.getClass().getMethods();
for (Method method : methods) {
try {
String name = method.getName();
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())// 方法是 public 的 setting 方法。
&& method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {// 方法的唯一参数是基本数据类型
// 获得属性名,例如 `ApplicationConfig#setName(...)` 方法,对应的属性名为 name 。
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), ".");
// 【启动参数变量】优先从带有 `Config#id` 的配置中获取,例如:`dubbo.application.demo-provider.name` 。
String value = null;
if (config.getId() != null && config.getId().length() > 0) {
String pn = prefix + config.getId() + "." + property;// 带有 `Config#id`
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
// 【启动参数变量】获取不到,其次不带 `Config#id` 的配置中获取,例如:`dubbo.application.name` 。
if (value == null || value.length() == 0) {
String pn = prefix + property;//不带 `Config#id`
value = System.getProperty(pn);
if (!StringUtils.isBlank(value)) {
logger.info("Use System Property " + pn + " to config dubbo");
}
}
if (value == null || value.length() == 0) {
// 覆盖优先级为:启动参数变量 > XML 配置 > properties 配置,因此需要使用 getter 判断 XML 是否已经设置
Method getter;
try {
getter = config.getClass().getMethod("get" + name.substring(3));
} catch (NoSuchMethodException e) {
try {
getter = config.getClass().getMethod("is" + name.substring(3));
} catch (NoSuchMethodException e2) {
getter = null;
}
}
if (getter != null) {
if (getter.invoke(config) == null) {// 使用 getter 判断 XML 是否已经设置
// 【properties 配置】优先从带有 `Config#id` 的配置中获取,例如:`dubbo.application.demo-provider.name` 。
if (config.getId() != null && config.getId().length() > 0) {
value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
}
// 【properties 配置】获取不到,其次不带 `Config#id` 的配置中获取,例如:`dubbo.application.name` 。
if (value == null || value.length() == 0) {
value = ConfigUtils.getProperty(prefix + property);
}
// 【properties 配置】老版本兼容,获取不到,最后不带 `Config#id` 的配置中获取,例如:`dubbo.protocol.name` 。
if (value == null || value.length() == 0) {
String legacyKey = legacyProperties.get(prefix + property);
if (legacyKey != null && legacyKey.length() > 0) {
value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
}
}
}
}
}
// 获取到值,进行反射设置。
if (value != null && value.length() > 0) {
method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);//逻辑中间发生异常,不抛出异常,仅打印错误日志。
}
}
}
附录:
- 芋道源码-dubbo源码
- dubbo官方文档-配置
- 博客-Dubbo Config