AnnotationPropertyValuesAdapter
AnnotationPropertyValuesAdapter
该类实现PropertyValues接口,这个接口主要是存放一个Bean的属性值集合,就可以理解为一个map。
内部有一个PropertyValues delegate属性。有三个重载的构造函数。说一下参数最全的两个构造函数
public AnnotationPropertyValuesAdapter(Annotation annotation, PropertyResolver propertyResolver,
boolean ignoreDefaultValue, String... ignoreAttributeNames) {
this.delegate = new MutablePropertyValues(getAttributes(annotation, propertyResolver, ignoreDefaultValue, ignoreAttributeNames));
}
getAttributes是AnnotationUtils类的一个方法(AnnotationUtils是阿里com.alibaba.spring.util包的内容),getAttributes方法返回类型为Map<String, Object>,该方法的主要作用就是解析注解的属性kv值并填充到返回的map中,ignoreDefaultValue和ignoreAttributeNames是配合使用的,如果前者为true,那么后者就可以提供想要忽略的属性值,这样最终就不会填充到返回的map中。new MutablePropertyValues接受的类型为map,MutablePropertyValues是PropertyValues一个实现类。
这也是这个类被称之为Annotation + Adapter的原因,spring没有提供注解解析并映射为PropertyValues的实现类,所以搞了一个适配器,利用AnnotationUtils#getAttributes解析并映射并构建MutablePropertyValues。
public AnnotationPropertyValuesAdapter(Map<String, Object> attributes, PropertyResolver propertyResolver,
String... ignoreAttributeNames) {
this.delegate = new MutablePropertyValues(getAttributes(attributes, propertyResolver, ignoreAttributeNames));
}
这个重载构造函数,第一个参数不是注解实例,而直接是一个map,然后依然调用阿里的重载的AnnotationUtils#getAttributes方法,整体逻辑和前面说的构造函数落差不多,毕竟前面的注解实例解析后也是一个和(该构造函数)第一个参数类似的map。
实现PropertyValues接口需要重写如下方法,这些方法就和map的kv存取差不多。
@Override
public PropertyValue[] getPropertyValues() {
return delegate.getPropertyValues();
}
@Override
public PropertyValue getPropertyValue(String propertyName) {
return delegate.getPropertyValue(propertyName);
}
@Override
public PropertyValues changesSince(PropertyValues old) {
return delegate.changesSince(old);
}
@Override
public boolean contains(String propertyName) {
return delegate.contains(propertyName);
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
AnnotationPropertyValuesAdapterTest
该测试类主要测试了上面的AnnotationPropertyValuesAdapter,内部使用了spring的相关api:包括MockEnvironment、 ReflectionUtils.findField、AnnotationUtils.getAnnotation、DataBinder、DefaultConversionService等。
(1)首先搞了一个MockEnvironment,这个是spring的 Environment 接口子类,Environment主要是解析替换placeHolder的,就是那些 x x 的 。 然 后 直 接 k v 方 式 存 入 , 这 样 遇 到 {xx}的。然后直接kv方式存入,这样遇到 xx的。然后直接kv方式存入,这样遇到{url}的就会替换为"1.0.0"。
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("version", "1.0.0");
mockEnvironment.setProperty("url", " dubbo://localhost:12345");
(2)使用spring的ReflectionUtils.findField、AnnotationUtils.getAnnotation现有工具类来获取TestBean类的demoService方法上的Reference注解(TestBean类见最后)。如下,注意@Reference标记为废弃了,最新版本使用@DubboService和@DubboReference。
Field field = ReflectionUtils.findField(TestBean.class, "demoService");
Reference reference = AnnotationUtils.getAnnotation(field, Reference.class);
(3)使用spring的DataBinder,构建DataBinder对象并指定要绑定到的目标对象ReferenceBean实例、指定忽略的填充字段、类型转化器DefaultConversionService、PropertyValues实例、最后一步调用dataBinder.bind(propertyValues);即可把PropertyValues的值填充到实例中(当然bind内部逻辑涉及到哪些属性忽略,以及特定类型属性的值转化,提供的两个类型转化器,用了spring的StringUtils#arrayToCommaDelimitedString以及dubbo的CollectionUtils.toStringMap),如下
ReferenceBean referenceBean = new ReferenceBean();
DataBinder dataBinder = new DataBinder(referenceBean);
dataBinder.setDisallowedFields("application", "module", "consumer", "monitor", "registry");
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter((Converter<String[], String>) source -> arrayToCommaDelimitedString(source));
conversionService.addConverter((Converter<String[], Map<String, String>>) source -> CollectionUtils.toStringMap(source));
dataBinder.setConversionService(conversionService);
AnnotationPropertyValuesAdapter propertyValues = new AnnotationPropertyValuesAdapter(reference, mockEnvironment);
dataBinder.bind(propertyValues);
TestBean如下
private static class TestBean {
@Reference(
interfaceClass = DemoService.class, interfaceName = "com.alibaba.dubbo.config.spring.api.DemoService",
version = "${version}", group = "group",
url = "${url} ", client = "client", generic = true, injvm = true,
check = false, init = true, lazy = true, stubevent = true,
reconnect = "reconnect", sticky = true, proxy = "javassist", stub = "stub",
cluster = "failover", connections = 1, callbacks = 1, onconnect = "onconnect",
ondisconnect = "ondisconnect", owner = "owner", layer = "layer", retries = 1,
loadbalance = "random", async = true, actives = 1, sent = true,
mock = "mock", validation = "validation", timeout = 2, cache = "cache",
filter = {"default", "default"}, listener = {"default", "default"}, parameters = {"key1", "value1"}, application = "application",
module = "module", consumer = "consumer", monitor = "monitor", registry = {"registry1", "registry2"}
)
private DemoService demoService;
}