Introduction to BeanWrapper Usage Scenarios Use of BeanWrapper source code Parsing property Settings and obtaining define property Settings and obtaining specific implementation constructor Settings Properties Obtain property references

An overview of the

After reading the Spring IoC ApplicationContext in the previous section, we have analyzed the five most important components in Spring IoC, but these are only the most important components involved in the IoC container. There are many other components that play an important role. So the rest of this article will cover some of the other classes as necessary.

Org. Springframework. Beans. BeanWrapper is an important component in the Spring framework classes. In Spring IoC’s AbstractBeanFactory(2) chapter, we introduce the creation process of various Scope beans. In the doCreateBean() method, the createBeanInstance(beanName, MBD, args) method is called before the bean instance is populated with properties to simply initialize an instance that is not of the type we wanted at the end. It’s a BeanWrapper instance. This chapter is a brief introduction to this type.

BeanWrapper profile

BeanWrapper is the central interface to Spring’s low-level JavaBeans infrastructure and acts as a proxy that provides operations for parsing and manipulating standard JavaBeans: The ability to get and set property values (individually or in batches), get property descriptors, and query properties for readability/writability. The BeanWrapper is mostly used inside Spring IoC, where the Spring IoC container can access bean properties in a uniform way. Users rarely need to program directly with BeanWrapper.

Let’s review the bean instantiation process and see how Spring uses BeanWrapper.

The bean instance procedure:

  • ResourceLoader loads configuration information and generates Resource.
  • BeanDefinitionReader reads and parses labels, converts the attributes of the label to the corresponding attributes of BeanDefinition, and registers them in the BeanDefinitionRegistry table;
  • Container scans the BeanDefinitionRegistry registry, Through the reflection mechanism (specific implementation is in the refresh method invokeBeanFactoryPostProcessors method) for spring BeanFactoryPostProcessor post-processor types of factories, The processor is used to process the BeanDefinition.
  • Instantiate the bean based on the processed BeanDefinition. BeanWrapper then assigns values to bean properties in combination with BeanDefinitionRegistry and PropertyEditorRegistry.

During the above bean instantiation, BeanWrapper fetches the property values defined in the XML file, converts the string in the XML file to the corresponding type of the property in the bean through the property editor or type converter, and finally fills the bean instance with introversion.

In fact, the core function of the BeanWrapper interface is to read and set the bean properties, which are accessed through Java introspection. To get a better idea of the BeanWrapper interface, here is an example of how to use BeanWrapper to access bean properties.

BeanWrapper usage scenario

In the process of creating bean, namely AbstractAutowireCapableBeanFactory doCreateBean in class () method, the BeanWrapper framework used to encapsulate instantiation of the bean. First create a BeanWrapper object by executing createBeanInstance(). In this method there is an autowireConstructor() method, the implementation of which is in the ConstructorResolver class:

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor
        [] chosenCtors, @Nullable Object[] explicitArgs) {

    BeanWrapperImpl bw = new BeanWrapperImpl();

    this.beanFactory.initBeanWrapper(bw);

Constructor<? > constructorToUse =null;

    ConstructorResolver.ArgumentsHolder argsHolderToUse = null;

    Object[] argsToUse = null;



.



Assert.state(argsToUse ! =null."Unresolved constructor arguments");

    bw.setBeanInstance(this.instantiate(beanName, mbd, constructorToUse, argsToUse));

    return bw;

}

Copy the code

This method is the process of creating the BeanWrapper object. This method will not be analyzed too much, but you can learn by yourself.

The use of the BeanWrapper

Create two new bean classes: Man and Car.

Car.java

public class Car {

    private int maxSpeed ;

    private String brand ;

    private double price ;



    public int getMaxSpeed(a) {

        return maxSpeed;

    }



    public void setMaxSpeed(int maxSpeed) {

        this.maxSpeed = maxSpeed;

    }



    public String getBrand(a) {

        return brand;

    }



    public void setBrand(String brand) {

        this.brand = brand;

    }



    public double getPrice(a) {

        return price;

    }



    public void setPrice(double price) {

        this.price = price;

    }



    @Override

    public String toString(a) {

        return "Car{" +

                "maxSpeed=" + maxSpeed +

                ", brand='" + brand + '\' ' +

                ", price=" + price +

                '} ';

    }

}

Copy the code

Man.java

public class Man {

    private String name;

    private int age;

    private Car car;

    private String[] hobbies;

    private Map<String,Object> relatives;



    public String getName(a) {

        return name;

    }



    public void setName(String name) {

        this.name = name;

    }



    public int getAge(a) {

        return age;

    }



    public void setAge(int age) {

        this.age = age;

    }



    public Car getCar(a) {

        return car;

    }



    public void setCar(Car car) {

        this.car = car;

    }



    public String[] getHobbies() {

        return hobbies;

    }



    public void setHobbies(String[] hobbies) {

        this.hobbies = hobbies;

    }



    public Map<String, Object> getRelatives(a) {

        return relatives;

    }



    public void setRelatives(Map<String, Object> relatives) {

        this.relatives = relatives;

    }



    @Override

    public String toString(a) {

        return "Man{" +

                "name='" + name + '\' ' +

                ", age=" + age +

                ", car=" + car +

                '} ';

    }

}

Copy the code

The test code

@Test

public void doBeanWrapper(a){

    Car car = new Car();

    BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);

    PropertyValue brandValue = new PropertyValue("brand"."Dongfeng");

    PropertyValue maxSpeedValue = new PropertyValue("maxSpeed".333);

    PropertyValue priceValue = new PropertyValue("price".202020);



    beanWrapperOfCar.setPropertyValue(brandValue);

    beanWrapperOfCar.setPropertyValue(maxSpeedValue);

    beanWrapperOfCar.setPropertyValue(priceValue);



    Man person = new Man();

    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);



    PropertyValue nameValue = new PropertyValue("name"."hresh");

    PropertyValue ageValue = new PropertyValue("age".23);

    PropertyValue carValue = new PropertyValue("car",car);

    String[] hobbies = {"Running"."Sing"."Reading"};

    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);

    Map<String,Object> relatives = new HashMap<>();

    relatives.put("Father"."hresh");

    relatives.put("Son"."hresh");

    PropertyValue relativeValue = new PropertyValue("relatives",relatives);



    beanWrapper.setPropertyValue(nameValue);

    beanWrapper.setPropertyValue(ageValue);

    beanWrapper.setPropertyValue(carValue);

    beanWrapper.setPropertyValue(hobbyValue);

    beanWrapper.setPropertyValue(relativeValue);



    System.out.println(person);

    System.out.println(beanWrapper.getWrappedInstance());

    System.out.println(person == beanWrapper.getWrappedInstance());



    int age = (Integer) beanWrapper.getPropertyValue("age");

    String hobby = (String) beanWrapper.getPropertyValue("hobbies[1]");

    String brand = (String) beanWrapper.getPropertyValue("car.brand");

    String relative = (String) beanWrapper.getPropertyValue("relatives['Father']");



    String result = String.format("%s is %d years old ,is interested in %s, has a relative named %s , also has a %s car !",person.getName(),age,hobby,relative,brand);

    System.out.println(result);

}

Copy the code

The execution result is as follows:

Man{name='hresh', age=23, car=Car{maxSpeed=333, brand='the east', price=202020.0}}

Man{name='hresh', age=23, car=Car{maxSpeed=333, brand='the east', price=202020.0}}

true

hresh is 23Years old,is interested in singing, has a relative named Hresh, also has a dongfeng car!

Copy the code

The result is an implementation of setting and retrieving bean properties for BeanWrapper. With that in mind, let’s take a look at the source code.

BeanWrapper source parsing

The BeanWrapper source code is defined as follows:

public interface BeanWrapper extends ConfigurablePropertyAccessor {

    // Specify limits for automatic growth of arrays and collections.

    void setAutoGrowCollectionLimit(int var1);



    // Returns limits on automatic growth of arrays and collections.

    int getAutoGrowCollectionLimit(a);



    // Returns the bean instance wrapped by this object.

    Object getWrappedInstance(a);



    // Returns the type of wrapped bean instance.

Class<? > getWrappedClass();



    // Get the PropertyDescriptors of the wrapped object (as determined by standard JavaBean introspection).

    PropertyDescriptor[] getPropertyDescriptors();



    // Gets the property descriptor for the specific property of the wrapper object.

    PropertyDescriptor getPropertyDescriptor(String var1) throws InvalidPropertyException;

}

Copy the code

BeanWrapper interface itself didn’t have a lot of the definition of content, but inherited ConfigurablePropertyAccessor interface, This interface also inherits PropertyAccessor, PropertyEditorRegistry, TypeConverter three interfaces. Their structural relationship diagram is as follows:

Property Settings and get definitions

Methods are defined in the PropertyAccessor interface to give BeanWrapper the ability to access bean properties. The PropertyAccessor interface is defined as follows:

public interface PropertyAccessor {

    // Path separator for nested properties.

    String NESTED_PROPERTY_SEPARATOR = ".";

    char NESTED_PROPERTY_SEPARATOR_CHAR = '. ';

    String PROPERTY_KEY_PREFIX = "[";

    char PROPERTY_KEY_PREFIX_CHAR = '[';

    String PROPERTY_KEY_SUFFIX = "]";

    char PROPERTY_KEY_SUFFIX_CHAR = '] ';



    // Determines whether the specified attribute is readable.

    boolean isReadableProperty(String var1);



    // Determines whether the specified attribute is writable.

    boolean isWritableProperty(String var1);



    // Determine the attribute type for the specified attribute, or check the attribute descriptor, or check the value in the case of index or mapped elements.

    @Nullable

Class<? > getPropertyType(String var1)throws BeansException;



    // Returns the type descriptor for the specified property: preferably from the read method to the write method.

    @Nullable

    TypeDescriptor getPropertyTypeDescriptor(String var1) throws BeansException;



    // Gets the current value of the specified property.

    @Nullable

    Object getPropertyValue(String var1) throws BeansException;



    // Sets the specified value to the current property value.

    void setPropertyValue(String var1, @Nullable Object var2) throws BeansException;



    // Sets the specified value to the current property value.

    void setPropertyValue(PropertyValue var1) throws BeansException;



    // Perform batch updates from map.

    void setPropertyValues(Map var1) throws BeansException;



    // The preferred method for performing batch updates.

    void setPropertyValues(PropertyValues var1) throws BeansException;



    // Perform batch updates for better control of behavior.

    void setPropertyValues(PropertyValues var1, boolean var2) throws BeansException;



    // Perform batch updates with full control over behavior.

    void setPropertyValues(PropertyValues var1, boolean var2, boolean var3) throws BeansException;

}

Copy the code

The PropertyAccessor interface provides methods to access bean properties. It also defines cleavers to access expressions that access nested properties, focusing on the following two methods:

Object getPropertyValue(String propertyName) throws BeansException;



void setPropertyValue(String propertyName, Object value) throws BeansException;

Copy the code

GetPropertyValue and setPropertyValue are used to get and set the bean property values, respectively. Here the propertyName supports expressions:

expression instructions
name Points to the property name, corresponding to getName() and setName()
car.brand A nested attribute brand that points to the attribute car, corresponding to getCar().getBrand() and getCar().setbrand ()
hobbies[1] Points to the second element of the index property hobbies, which could be an array, list, or other naturally ordered container
relatives['Father'] Indicates the corresponding value of Father as the key in the Map entity that has relatives

Property setting and getting the concrete implementation

The implementation class for BeanWrapper is BeanWrapperImpl, which wraps the bean object, caches the bean’s introspective results, and can access bean properties and set bean property values. In addition, the BeanWrapperImpl class provides a default property editor that supports many different types of type conversions. For example, you can convert an array or collection type property to an array or collection of a specific type. Users can also register custom property editors in BeanWrapperImpl.

BeanWrapperImpl, moreover, there is a cachedIntrospectionResults member variable, it saves the packaged bean introspection analysis results. CachedIntrospectionResults has two member variables, one is a beanInfo, it is wrapped beanInfo classes; The other one is propertyDescriptorCache, which caches the PropertyDescriptor of all the properties of the wrapped class.

Let’s look at the structure class diagram of BeanWrapperImpl.

Then, the important methods in BeanWrapperImpl are interpreted.

A constructor

In the test case, there is this line of code:

BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);

Copy the code

The actual implementation is new BeanWrapperImpl(Target), which is just one constructor in BeanWrapperImpl. This class overloads many constructors. Let’s look at the constructor for bean instances as parameters.

public BeanWrapperImpl(Object object) {

    super(object);

}

Copy the code

Call the superclass AbstractNestablePropertyAccessor constructor.

protected AbstractNestablePropertyAccessor(Object object) {

    this.autoGrowCollectionLimit = 2147483647;

    this.nestedPath = "";

    // The identity can use the default property editor

    this.registerDefaultEditors();

    this.setWrappedInstance(object);

}

Copy the code
public void setWrappedInstance(Object object) {

    this.setWrappedInstance(object, "", (Object)null);

}



public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {

    // The bean is set to the internal variable of BeanWrapperImpl

    this.wrappedObject = ObjectUtils.unwrapOptional(object);

    Assert.notNull(this.wrappedObject, "Target object must not be null");

    this.nestedPath = nestedPath ! =null ? nestedPath : "";

    this.rootObject = !this.nestedPath.isEmpty() ? rootObject : this.wrappedObject;

    this.nestedPropertyAccessors = null;

    / / the delegate class of a new type converter, BeanWrapperImpl instance for propertyEditorRegistry here, the beans for targetObject

    this.typeConverterDelegate = new TypeConverterDelegate(this.this.wrappedObject);

}

Copy the code

You can see from the above code that the constructor does two important things: it sets the bean passed in as an internal variable, and it instantiates a delegate class for a type converter. Because BeanWrapperImpl inherited PropertyEditorRegistrySupport at the same time, So it exists in the TypeConverterDelegate as a help class in the Property editor registry of the TypeConverterDelegate.

Set properties

BeanWrapperImpl supports a variety of ways to set the bean properties, but are in the superclass AbstractNestablePropertyAccessor inheritance.

First look at the setPropertyValue(String propertyName, @Nullable Object Value) method.

public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {

    AbstractNestablePropertyAccessor nestedPa;

    try {

        // Get BeanWrapImpl object according to attribute name, support multiple attribute recursive analysis processing

        nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);

    } catch (NotReadablePropertyException var5) {

        throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var5);

    }



     // After the above recursion, we get the object of the property to be operated on. Based on this property object, we will get the property of the embedded object to be operated on.

    // Generate PropertyTokenHolder, introspectively set the property value

    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));

    nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));

}

Copy the code

GetPropertyAccessorForPropertyPath expression according to the attribute name () methods to obtain access to the attributes of the property accessor AbstractNestablePropertyAccessor, namely BeanWrapperImpl. Here is an example of what an attribute name expression means.

@Test

public void doBeanWrapper(a){

    Car car = new Car();



    Man person = new Man();

    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);



    PropertyValue nameValue = new PropertyValue("name"."hresh");

    PropertyValue ageValue = new PropertyValue("age".23);

    PropertyValue carValue = new PropertyValue("car",car);

    String[] hobbies = {"Running"."Sing"."Reading"};

    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);

    Map<String,Object> relatives = new HashMap<>();

    relatives.put("Father"."hresh");

    relatives.put("Son"."hresh");

    PropertyValue relativeValue = new PropertyValue("relatives",relatives);



    // beanWrapper.setPropertyValue(nameValue);

    beanWrapper.setPropertyValue("name"."hresh");

    beanWrapper.setPropertyValue(ageValue);

    beanWrapper.setPropertyValue(carValue);

    beanWrapper.setPropertyValue("car.brand"."The East is red");

    beanWrapper.setPropertyValue(hobbyValue);

    beanWrapper.setPropertyValue("hobbies[1]"."Dance");

    beanWrapper.setPropertyValue(relativeValue);

    beanWrapper.setPropertyValue("relatives['Father']"."clearLove");



    System.out.println(person);



}

Copy the code

PropertyName is the name of the property, it could be just a normal string like “name”, it could be an expression like “car. Brand “, so let’s see what it does when we have nested properties like this. Specific see getPropertyAccessorForPropertyPath () method.

protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {

    // In the case of nested attributes, get the position of the first nested attribute, delimited by "."

    int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);

    // Get nested properties recursively if propertyPath no longer has separator ". Returns the recursive result

    if (pos > -1) {

        String nestedProperty = propertyPath.substring(0, pos);

        String nestedPath = propertyPath.substring(pos + 1);

        / / to get from the recursive AbstractNestablePropertyAccessor properties

        AbstractNestablePropertyAccessor nestedPa = this.getNestedPropertyAccessor(nestedProperty);

        return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);

    } else {

        return this;

    }

}

Copy the code

Combined with the test case above, we look at in the process of debugging results show, when performing beanWrapper. SetPropertyValue (” name “, “hresh”),

The returned property accessor (that is, BeanWrapperImpl) contains bean property values of the Man class.

When performing beanWrapper. SetPropertyValue (” car brand “, “east is red”); The statement,

If an embedded attribute is found, it is split and processed in order, with the first attribute, the “CAR” attribute, being processed first. Now call getNestedPropertyAccessor () method, watching the definition of the method:

private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {

    if (this.nestedPropertyAccessors == null) {

        this.nestedPropertyAccessors = new HashMap();

    }



    // Get PropertyTokenHolder based on the property name

    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = this.getPropertyNameTokens(nestedProperty);

    / / value of the car

    String canonicalName = tokens.canonicalName;

    // Get the instantiated object of the embedded property according to the PropertyTokenHolder, in this case the CAR object

    Object value = this.getPropertyValue(tokens);

    // Since we already setPropertyValue(carValue), the value we get here

    if (value == null || value instanceofOptional && ! ((Optional)value).isPresent()) {

        // Call setDefaultValue to create the default object if automatic creation of attributes is allowed, otherwise throw an exception

        if (!this.isAutoGrowNestedPaths()) {

            throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + canonicalName);

        }



        value = this.setDefaultValue(tokens);

    }



    // Wrap the nested object instance as a new BeanWrapperImpl, and then cache the BeanWrapperImpl into the cache object nestedPropertyAccessors.

    AbstractNestablePropertyAccessor nestedPa = (AbstractNestablePropertyAccessor)this.nestedPropertyAccessors.get(canonicalName);

    if(nestedPa ! =null && nestedPa.getWrappedInstance() == ObjectUtils.unwrapOptional(value)) {

        if (logger.isTraceEnabled()) {

            logger.trace("Using cached nested property accessor for property '" + canonicalName + "'");

        }

    } else {

        For example, in the case of the embedded property "car. Brand", when handling the car, the acquired car object is encapsulated as PropertyAccessor and cached in nestedPropertyAccessors

        if (logger.isTraceEnabled()) {

            logger.trace("Creating new nested " + this.getClass().getSimpleName() + " for property '" + canonicalName + "'");

        }



        nestedPa = this.newNestedPropertyAccessor(value, this.nestedPath + canonicalName + ".");

        this.copyDefaultEditorsTo(nestedPa);

        this.copyCustomEditorsTo(nestedPa, canonicalName);

        this.nestedPropertyAccessors.put(canonicalName, nestedPa);

    }



    return nestedPa;

}

Copy the code

The debugging result when executing this method is as follows:

In the code above, getPropertyNameTokens generates a unified action structure PropertyTokenHolder based on the propertyName, which holds the resolved structure of the propertyName. As we mentioned earlier, propertyName supports three formats, so there are three parsed structures. Combined with the above cases,

When propertyPath as “car. The brand”, as a result of segmentation operation, will be called getNestedPropertyAccessor () method, after entering the method, will perform getPropertyNameTokens (nestedProperty) method, In this case, nestedProperty is equivalent to propertyName and the value is car. The analytical results are as follows:

When propertyPath is “hobbies[1]”, the program goes from setPropertyValue(Value) to getPropertyNameTokens(propertyName). The propertyName value is “hobbies[1]”. The analytical results are as follows:

When propertyPath is “relatives[‘Father’]”, It’s going to go from setPropertyValue to getPropertyNameTokens, In this case, the propertyName value is “relatives[‘Father’]”. The analytical results are as follows:

Let’s look at the implementation of the getPropertyNameTokens() method.

private AbstractNestablePropertyAccessor.PropertyTokenHolder getPropertyNameTokens(String propertyName) {

    String actualName = null;

    List<String> keys = new ArrayList(2);

    int searchIndex = 0;



    while(true) {

        int keyStart;

        int keyEnd;

        do {

            do {

                if (searchIndex == -1) {

                    // Generate PropertyTokenHolder object, update actualName, canonicalName, keys contents

                    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = newAbstractNestablePropertyAccessor.PropertyTokenHolder(actualName ! =null ? actualName : propertyName);

                    if(! keys.isEmpty()) {

                        tokens.canonicalName = tokens.canonicalName + "[" + StringUtils.collectionToDelimitedString(keys, "] [") + "]";

                        tokens.keys = StringUtils.toStringArray(keys);

                    }



                    return tokens;

                }



                // Check whether the propertyName is an array, list, map, etc., with [], and get the index of [. If it is a normal string, the keyStart value is -1

                // May be a two-dimensional array, similar to "nums[1][1]", so continue fetching the key

                keyStart = propertyName.indexOf("[", searchIndex);

                searchIndex = -1;

            } while(keyStart == -1);



            // Calculates the index of the next character "]"

            keyEnd = this.getPropertyNameKeyEnd(propertyName, keyStart + "[".length());

        } while(keyEnd == -1);



        // Get the name of the collection, whether array, list, or map

        if (actualName == null) {

            actualName = propertyName.substring(0, keyStart);

        }



        String key = propertyName.substring(keyStart + "[".length(), keyEnd);

        // If it is a map set, get the key value

        if (key.length() > 1 && key.startsWith("'") && key.endsWith("'") || key.startsWith("\" ") && key.endsWith("\" ")) {

            key = key.substring(1, key.length() - 1);

        }



        keys.add(key);

        searchIndex = keyEnd + "]".length();

    }

}

Copy the code

As for the case of the two-dimensional array mentioned in the code comment, I conducted the test operation, and the screenshot of this part is as follows:

In getNestedPropertyAccessor () there is a more important code:

Object value = this.getPropertyValue(tokens);

Copy the code

This method gets an instance of the specified property based on the PropertyTokenHolder. Take a look at this code.

protected Object getPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens) throws BeansException {

    String propertyName = tokens.canonicalName;

    String actualName = tokens.actualName;

    / / get PropertyHandler, internal implementation is in BeanWrapperImpl class, from PropertyDescriptor cachedIntrospectionResults to retrieve the property,

    // Then retrieve the PropertyType, ReadMethod, WriteMethod properties

    AbstractNestablePropertyAccessor.PropertyHandler ph = this.getLocalPropertyHandler(actualName);

    // Attribute unreadable, throw exception

    if(ph ! =null && ph.isReadable()) {

        try {

            // Get an instance of the attribute in introspection mode

            Object value = ph.getValue();

            // The keys of tokens are not empty, and the keys hold the index numbers that need to be accessed.

            // In map, keys is a string

            // Here is how to get the attribute value of a particular subscript using the index number.

            if(tokens.keys ! =null) {

                if (value == null) {

                    if (!this.isAutoGrowNestedPaths()) {

                        throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed property path '" + propertyName + "': returned null");

                    }



                    value = this.setDefaultValue(new AbstractNestablePropertyAccessor.PropertyTokenHolder(tokens.actualName));

                }



                StringBuilder indexedPropertyName = new StringBuilder(tokens.actualName);



                / / if it is a multidimensional array, such as a two-dimensional array nums [2] [3], when to modify nums [1] [2] the value of the, at this point after getPropertyNameTokens method returns the value of the token keys should be two, But when the propertyPath passed in is not of the car.brand form, such as an array, a list, etc., it goes from setPropertyValue() to getPropertyValue(), It also goes through the processKeyedProperty() and getPropertyHoldingValue() methods, especially the getPropertyHoldingValue() method, where, It intercepts the keys in the acquired token. The keys in the token obtained by the two-dimensional array should have two values, but only one value is entered in this method.

                for(int i = 0; i < tokens.keys.length; ++i) {

                    String key = tokens.keys[i];

                    if (value == null) {

                        throw new NullValueInNestedPathException(this.getRootClass(), this.nestedPath + propertyName, "Cannot access indexed value of property referenced in indexed property path '" + propertyName + "': returned null");

                    }



                    int index;

                    // Make the difference according to the type

                    if (value.getClass().isArray()) {

                        index = Integer.parseInt(key);

                        value = this.growArrayIfNecessary(value, index, indexedPropertyName.toString());

                        value = Array.get(value, index);

                    } else if (value instanceof List) {

                        index = Integer.parseInt(key);

                        List<Object> list = (List)value;

                        this.growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);

                        value = list.get(index);

                    } else if (value instanceof Set) {

                        Set<Object> set = (Set)value;

                        int index = Integer.parseInt(key);

                        if (index < 0 || index >= set.size()) {

                            throw new InvalidPropertyException(this.getRootClass(), this.nestedPath + propertyName, "Cannot get element with index " + index + " from Set of size " + set.size() + ", accessed using property path '" + propertyName + "'");

                        }



                        Iterator<Object> it = set.iterator();



                        for(int j = 0; it.hasNext(); ++j) {

                            Object elem = it.next();

                            if (j == index) {

                                value = elem;

                                break;

                            }

                        }

                    } else {

                        if(! (valueinstanceof Map)) {

                            throw new InvalidPropertyException(this.getRootClass(), this.nestedPath + propertyName, "Property referenced in indexed property path '" + propertyName + "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");

                        }



                        Map<Object, Object> map = (Map)value;

Class<? > mapKeyType = ph.getResolvableType().getNested(i +1).asMap().resolveGeneric(new int[] {0});

                        TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);

                        Object convertedMapKey = this.convertIfNecessary((String)null, (Object)null, key, mapKeyType, typeDescriptor);

                        value = map.get(convertedMapKey);

                    }



                    indexedPropertyName.append("[").append(key).append("]");

                }

            }



            return value;

        } 

.

    }

else {

    throw new NotReadablePropertyException(this.getRootClass(), this.nestedPath + propertyName);

}

}

Copy the code

In addition to getNestedPropertyAccessor () method into the getPropertyValue () method, when the incoming propertyName parameter value is an array, collection, list, Map, etc., the is another way to go, I drew a diagram to show:

When you add a 2d array property to the Man class in the test case, and then debug it, you can see the difference in the getPropertyValue() method as follows:

@Test

public void doBeanWrapper(a){

    Car car = new Car();



    Man person = new Man();

    BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(person);



    PropertyValue nameValue = new PropertyValue("name"."hresh");

    PropertyValue ageValue = new PropertyValue("age".23);

    PropertyValue carValue = new PropertyValue("car",car);

    String[] hobbies = {"Running"."Sing"."Reading"};

    PropertyValue hobbyValue = new PropertyValue("hobbies",hobbies);

    Map<String,Object> relatives = new HashMap<>();

    relatives.put("Father"."hresh");

    relatives.put("Son"."hresh");

    PropertyValue relativeValue = new PropertyValue("relatives",relatives);

    Integer[][] nums = {{1.2.3}, {4.5.6}};

    PropertyValue numsValue = new PropertyValue("nums",nums);



    // beanWrapper.setPropertyValue(nameValue);

    beanWrapper.setPropertyValue("name"."hresh");

    beanWrapper.setPropertyValue(ageValue);

    beanWrapper.setPropertyValue(carValue);

    beanWrapper.setPropertyValue("car.brand"."The East is red");

    beanWrapper.setPropertyValue(hobbyValue);

    beanWrapper.setPropertyValue("hobbies[1]"."Dance");

    beanWrapper.setPropertyValue(relativeValue);

    beanWrapper.setPropertyValue("relatives['Father']"."clearLove");

    beanWrapper.setPropertyValue(numsValue);

    beanWrapper.setPropertyValue("nums[1][2]".22);



    System.out.println(person);



}

Copy the code

Summary: When setting a bean property via setPropertyValue, the property is first recursively retrieved from the string of the propertyName property (if the property is not embedded, it is itself), and then the value of the property is set introspectively.

Retrieve attributes

BeanWrapperImpl has two methods for getting attributes.

public Object getPropertyValue(String propertyName) throws BeansException



protected Object getPropertyValue(PropertyTokenHolder tokens)

Copy the code

GetPropertyValue (PropertyTokenHolder Tokens) is used internally and not publicly.

In that case, take a look at the getPropertyValue(propertyName) method. The method is realized in AbstractNestablePropertyAccessor.

public Object getPropertyValue(String propertyName) throws BeansException {

    AbstractNestablePropertyAccessor nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);

    AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));

    return nestedPa.getPropertyValue(tokens);

}

Copy the code

Including getPropertyAccessorForPropertyPath () method in the previous section have been detailed analysis, it will be recursive AbstractNestablePropertyAccessor can get access to the properties, The implementation class here is BeanWrapperImpl. For example, propertyName is car. Brand, then BeanWrapperImpl wraps an instance of Car. Then perform getPropertyNameTokens(tokens) to obtain the token value.

reference

https://my.oschina.net/thinwonton/blog/1492224#h1_3