AnnotationPropertyValuesAdapter

AnnotationPropertyValuesAdapter

This class implements the PropertyValues interface, which mainly stores a set of Bean PropertyValues, which can be understood as a map. There is only one property inside, as follows

private final PropertyValues delegate;
Copy the code

There are three overloaded constructors. Let’s talk about the two constructors with the most complete arguments

public AnnotationPropertyValuesAdapter(Annotation annotation, PropertyResolver propertyResolver,
                                       boolean ignoreDefaultValue, String... ignoreAttributeNames) {
    this.delegate = new MutablePropertyValues(getAttributes(annotation, propertyResolver, ignoreDefaultValue, ignoreAttributeNames));
}
Copy the code

GetAttributes is a method of the AnnotationUtils class (AnnotationUtils is part of the Alibaba.spring.util package), The getAttributes method returns Map<String, Object>. The main function of this method is to parse the attribute kv value of the annotation and populate it with the Map returned. If the former is true, then the latter can provide the attribute values you want to ignore so that they are not eventually populated into the returned map. New MutablePropertyValues accepts type map. MutablePropertyValues is an implementation class of PropertyValues.

That’s why this class is called Annotation + Adapter, spring doesn’t provide an implementation class for Annotation parsing and mapping to PropertyValues, so I made an Adapter, AnnotationUtils#getAttributes parsed, mapped, and built MutablePropertyValues, delegating the actual work to MutablePropertyValues. AnnotationPropertyValuesAdapter is the adapter, it can be fit to the annotations on the adapter PropertyValues information interface.


public AnnotationPropertyValuesAdapter(Map
       
         attributes, PropertyResolver propertyResolver, String... ignoreAttributeNames)
       ,> {
    this.delegate = new MutablePropertyValues(getAttributes(attributes, propertyResolver, ignoreAttributeNames));
}
Copy the code

The overloaded constructor, which takes a map instead of an annotation instance, then calls ali’s overloaded AnnotationUtils#getAttributes method. After all, the previous annotation instance parses a map similar to the first argument.


Implementing the PropertyValues interface requires rewriting the following methods, which are similar to kv access for map.

@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(a) {
    return delegate.isEmpty();
}
Copy the code

AnnotationPropertyValuesAdapterTest

The test class is mainly tested the above AnnotationPropertyValuesAdapter, internal use spring related API: Including MockEnvironment, ReflectionUtils. FindField, AnnotationUtils. GetAnnotation, DataBinder, DefaultConversionService, etc.

(1) First, we made a MockEnvironment, which is a subclass of Spring Environment interface. Environment is mainly resolved to replace placeHolder, that is, xx. And then directly kv way deposit, so encountered {xx}. And then directly deposited in KV mode, so encountered xx. Then store directly in KV mode, so that {url} will be replaced with “1.0.0”.

MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("version"."1.0.0"); 
mockEnvironment.setProperty("url"." dubbo://localhost:12345");
Copy the code

(2) use the spring ReflectionUtils findField, AnnotationUtils. GetAnnotation demoService method of existing tools to obtain TestBean class Reference on annotations (TestBean Class see last). Note below that the @reference flag is deprecated and the latest version uses @dubboService and @dubboReference.

Field field = ReflectionUtils.findField(TestBean.class, "demoService");
Reference reference = AnnotationUtils.getAnnotation(field, Reference.class);
Copy the code

(3) Use the Spring DataBinder Build the DataBinder object and specify the ReferenceBean instance of the target object to bind to, specify the fill field to ignore, the type converter DefaultConversionService, the PropertyValues instance, and, as a last step, call databinder.bind (propertyValues); You can then populate the instance with the PropertyValues value. (Of course, bind’s internal logic involves which properties to ignore, as well as converting values to specific types of properties. With the spring StringUtils# arrayToCommaDelimitedString and dubbo CollectionUtils. ToStringMap), as follows

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);
Copy the code

TestBean as follows

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;

}
Copy the code