Some time ago, I happened to encounter a scene to dynamically obtain Pojo class, and then perform some operations, and then assign value to another class. I immediately thought of using reflection to solve the problem, but I need to write a lot of code, and I feel that it is not very good, wondering if there is a third party has encapsulated API can be used? Just thought of Mybatis is using MetaObject to do ORM processing, can be borrowed to use (Mybatis source not white read ha). It’s nice to use, but how does MetaObject handle Pojo classes like a dynamic language? So I decided to read its source code carefully.

Let’s write an example:

package com.example.metdobjectanalysis.model;

public class Person {
    private String name;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name; }} -- -- -- -- -- -- --package com.example.metdobjectanalysis;

import com.example.metdobjectanalysis.model.Person;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MetdObjectAnalysisApplicationTests {

    @Test
    void getTest(a) {
        Person person = new Person();

        MetaObject metaObject = SystemMetaObject.forObject(person);
        String name = (String) metaObject.getValue("name");
        metaObject.setValue("name"."tom");
        System.out.println("The name is " + name);
        // The name is Tom}}Copy the code

The code is pretty straightforward and doesn’t need much explanation, so here’s the question:

  1. SystemMetaObject.forObjectWhat’s in it?
  2. MetaObjectHow does that work?

SystemMetaObject

public final class SystemMetaObject {

  public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
  public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
  public static final MetaObject NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

  private SystemMetaObject(a) {
    // Prevent Instantiation of Static Class
  }

  private static class NullObject {}public static MetaObject forObject(Object object) {
    return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, newDefaultReflectorFactory()); }}Copy the code

We can see that SystemMetaObject is a factory, which is only responsible for the default creation of MetaObject, encapsulating its constructor. We can see that creating MetaObject requires passing in four parameters, which are respectively the object to operate, ObjectFactory, ObjectWrapperFactory and ReflectorFactory.

ObjectFactory

Take a look at the ObjectFactory interface:

/**
 * MyBatis uses an ObjectFactory to create all needed new Objects.
 *
 * @author Clinton Begin
 */
public interface ObjectFactory {

  /**
   * Sets configuration properties.
   * @param properties configuration properties
   */
  default void setProperties(Properties properties) {
    // NOP
  }

  /**
   * Creates a new object with default constructor.
   *
   * @param <T>
   *          the generic type
   * @param type
   *          Object type
   * @return the t
   */
  <T> T create(Class<T> type);

  /**
   * Creates a new object with the specified constructor and params.
   *
   * @param <T>
   *          the generic type
   * @param type
   *          Object type
   * @param constructorArgTypes
   *          Constructor argument types
   * @param constructorArgs
   *          Constructor argument values
   * @return the t
   */
  <T> T create(Class
       
         type, List
        
         > constructorArgTypes, List
          constructorArgs)
        >
       ;

  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   *
   * @param <T>
   *          the generic type
   * @param type
   *          Object type
   * @return whether it is a collection or not
   * @since3.1.0 * /
  <T> boolean isCollection(Class<T> type);

}
Copy the code

This interface defines that it is responsible for creating objects. Let’s see how DefaultObjectFactory creates objects:

public class DefaultObjectFactory implements ObjectFactory.Serializable {

  private static final long serialVersionUID = -8855120656740914948L;

  @Override
  public <T> T create(Class<T> type) {
    return create(type, null.null);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T create(Class
       
         type, List
        
         > constructorArgTypes, List
          constructorArgs)
        >
        { Class<? > classToCreate = resolveInterface(type);// we know types are assignable
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }

  private  <T> T instantiateClass(Class
       
         type, List
        
         > constructorArgTypes, List
          constructorArgs)
        >
        {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        try {
          return constructor.newInstance();
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            constructor.setAccessible(true);
            return constructor.newInstance();
          } else {
            throw e;
          }
        }
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
      try {
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } catch (IllegalAccessException e) {
        if (Reflector.canControlMemberAccessible()) {
          constructor.setAccessible(true);
          return constructor.newInstance(constructorArgs.toArray(new Object[0]));
        } else {
          throwe; }}}catch (Exception e) {
      String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
          .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
      String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
          .stream().map(String::valueOf).collect(Collectors.joining(","));
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: "+ e, e); }}protectedClass<? > resolveInterface(Class<? > type) { Class<? > classToCreate;if (type == List.class || type == Collection.class || type == Iterable.class) {
      classToCreate = ArrayList.class;
    } else if (type == Map.class) {
      classToCreate = HashMap.class;
    } else if (type == SortedSet.class) { // issue #510 Collections Support
      classToCreate = TreeSet.class;
    } else if (type == Set.class) {
      classToCreate = HashSet.class;
    } else {
      classToCreate = type;
    }
    return classToCreate;
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    returnCollection.class.isAssignableFrom(type); }}Copy the code

Public

T create(Class

type, List

> constructorArgTypes, List
constructorArgs):
>

@Override
public <T> T create(Class
       
         type, List
        
         > constructorArgTypes, List
          constructorArgs)
        >
        { Class<? > classToCreate = resolveInterface(type);// we know types are assignable
  return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
Copy the code

ResolveInterface takes care of the interface and turns it into the corresponding implementation class:

protectedClass<? > resolveInterface(Class<? > type) { Class<? > classToCreate;if (type == List.class || type == Collection.class || type == Iterable.class) {
    classToCreate = ArrayList.class;
  } else if (type == Map.class) {
    classToCreate = HashMap.class;
  } else if (type == SortedSet.class) { // issue #510 Collections Support
    classToCreate = TreeSet.class;
  } else if (type == Set.class) {
    classToCreate = HashSet.class;
  } else {
    classToCreate = type;
  }
  return classToCreate;
}
Copy the code

InstantiateClass: instantiateClass: instantiateClass: instantiateClass: instantiateClass: instantiateClass: instantiateClass: instantiateClass: instantiateClass

private  <T> T instantiateClass(Class
       
         type, List
        
         > constructorArgTypes, List
          constructorArgs)
        >
        {
  try {
    Constructor<T> constructor;
    if (constructorArgTypes == null || constructorArgs == null) {
      constructor = type.getDeclaredConstructor();
      try {
        return constructor.newInstance();
      } catch (IllegalAccessException e) {
        if (Reflector.canControlMemberAccessible()) {
          constructor.setAccessible(true);
          return constructor.newInstance();
        } else {
          throw e;
        }
      }
    }
    constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
    try {
      return constructor.newInstance(constructorArgs.toArray(new Object[0]));
    } catch (IllegalAccessException e) {
      if (Reflector.canControlMemberAccessible()) {
        constructor.setAccessible(true);
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } else {
        throwe; }}}catch (Exception e) {
    String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
        .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
    String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
        .stream().map(String::valueOf).collect(Collectors.joining(","));
    throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: "+ e, e); }}Copy the code

Note here the error handling at the end, which uses Optional for null value error handling.

ObjectWrapperFactory

SystemMetaObject creates metaObjects that have no wrapper classes.

ReflectorFactory

public interface ReflectorFactory {

  boolean isClassCacheEnabled(a);

  void setClassCacheEnabled(boolean classCacheEnabled);

  Reflector findForClass(Class
        type);
}
Copy the code

If you look at the interface definition, you can assume two things, one is to use the cache, and the other is to use findForClass to find the reflector, look at DefaultReflectorFactory:

public class DefaultReflectorFactory implements ReflectorFactory {
  private boolean classCacheEnabled = true;
  private finalConcurrentMap<Class<? >, Reflector> reflectorMap =new ConcurrentHashMap<>();

  public DefaultReflectorFactory(a) {}@Override
  public boolean isClassCacheEnabled(a) {
    return classCacheEnabled;
  }

  @Override
  public void setClassCacheEnabled(boolean classCacheEnabled) {
    this.classCacheEnabled = classCacheEnabled;
  }

  @Override
  public Reflector findForClass(Class
        type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
      return newReflector(type); }}}Copy the code

Mybatis (Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis)

/** * This class represents a cached set of class definition information that * allows for easy mapping between property  names and getter/setter methods. * * @author Clinton Begin */ public class Reflector { private final Class<? > type; private final String[] readablePropertyNames; private final String[] writablePropertyNames; private final Map<String, Invoker> setMethods = new HashMap<>(); private final Map<String, Invoker> getMethods = new HashMap<>(); private final Map<String, Class<? >> setTypes = new HashMap<>(); private final Map<String, Class<? >> getTypes = new HashMap<>(); private Constructor<? > defaultConstructor; private Map<String, String> caseInsensitivePropertyMap = new HashMap<>(); public Reflector(Class<? > clazz) { type = clazz; addDefaultConstructor(clazz); addGetMethods(clazz); addSetMethods(clazz); addFields(clazz); readablePropertyNames = getMethods.keySet().toArray(new String[0]); writablePropertyNames = setMethods.keySet().toArray(new String[0]); for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); }}}Copy the code

There’s too much source code for me to put up, but I’ll just put up the constructor source code, which is enough to explain what it does, and the introduction says that it maps property names to getter/setter methods.

AddDefaultConstructor looks for the class constructor and adds an assignment to defaultConstructor if it has no arguments. AddGetMethods and addSetMethods are similar, Use the method name to determine whether it is a getter/setter, and do special processing accordingly.

Why use reflectorMap? So you can reuse the Reflector you created earlier? Why use ConcurrentHashMap? Because servers are typically multithreaded, using ConcurrentHashMap ensures that fetching and creating are atomic operations.

MetaObject

Finally, to talk about today’s protagonist MetaObject, first look at the source code:

public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object); }}public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
        returnmetaValue.getValue(prop.getChildren()); }}else {
      returnobjectWrapper.get(prop); }}public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null) {
          // don't instantiate child path if value is null
          return;
        } else {
          metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
        }
      }
      metaValue.setValue(prop.getChildren(), value);
    } else{ objectWrapper.set(prop, value); }}public MetaObject metaObjectForProperty(String name) {
    Object value = getValue(name);
    returnMetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory); }}Copy the code

ObjectWrapper is a proxy class that is only enhanced from the original class. Then you can see that its methods call ObjectWrapper a lot. MetaObject does some pre-processing and it is ObjectWrapper that actually operates on it. First look at the ObjectWrapper interface:

public interface ObjectWrapper {

  Object get(PropertyTokenizer prop);

  void set(PropertyTokenizer prop, Object value);

  String findProperty(String name, boolean useCamelCaseMapping); String[] getGetterNames(); String[] getSetterNames(); Class<? > getSetterType(String name); Class<? > getGetterType(String name);boolean hasSetter(String name);

  boolean hasGetter(String name);

  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

  boolean isCollection(a);

  void add(Object element);

  <E> void addAll(List<E> element);

}
Copy the code
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  this.originalObject = object;
  this.objectFactory = objectFactory;
  this.objectWrapperFactory = objectWrapperFactory;
  this.reflectorFactory = reflectorFactory;

  if (object instanceof ObjectWrapper) {
    this.objectWrapper = (ObjectWrapper) object;
  } else if (objectWrapperFactory.hasWrapperFor(object)) {
    this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  } else if (object instanceof Map) {
    this.objectWrapper = new MapWrapper(this, (Map) object);
  } else if (object instanceof Collection) {
    this.objectWrapper = new CollectionWrapper(this, (Collection) object);
  } else {
    this.objectWrapper = new BeanWrapper(this, object); }}Copy the code

Because we are the incoming DefaultObjectWrapperFactory, so in the end it will invoke the BeanWrapper:

public class BeanWrapper extends BaseWrapper {

  private final Object object;
  private final MetaClass metaClass;

  public BeanWrapper(MetaObject metaObject, Object object) {
    super(metaObject);
    this.object = object;
    this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory()); }... }Copy the code

As ReflectorFactory passes in MetaClass, it deals with reflection methods.

getValue/setValue

Because we’re going to do different things here, we’re going to do different things.

Let’s start with the simplest case, where only basic classes (e.g., String, Integer) are processed:

Basic class case

Once again, I’ve pasted the sample code:

package com.example.metdobjectanalysis.model;

public class Person {
    private String name;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name; }} -- -- -- -- -- -- --package com.example.metdobjectanalysis;

import com.example.metdobjectanalysis.model.Person;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MetdObjectAnalysisApplicationTests {

    @Test
    void contextLoads(a) {}@Test
    void getTest(a) {
        Person person = new Person();

        MetaObject metaObject = SystemMetaObject.forObject(person);
        String name = (String) metaObject.getValue("name");
        metaObject.setValue("name"."tom");
        System.out.println("The name is " + name);
        // The name is Tom}}Copy the code

MetaObject setValue does exactly what it does:

public void setValue(String name, Object value) {
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      if (value == null) {
        // don't instantiate child path if value is null
        return;
      } else {
        metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
      }
    }
    metaValue.setValue(prop.getChildren(), value);
  } else{ objectWrapper.set(prop, value); }}Copy the code

PropertyTokenizer is the parsing of the name of the property passed in, because the name of the property passed in can be a collection or a nested type, so the parsing is done. Since we now have a single property name, go directly to objectwrapper.set (prop, value) and then to the setBeanProperty method of BeanWrapper:

private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
  try {
    Invoker method = metaClass.getSetInvoker(prop.getName());
    Object[] params = {value};
    try {
      method.invoke(object, params);
    } catch (Throwable t) {
      throwExceptionUtil.unwrapThrowable(t); }}catch (Throwable t) {
    throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
Copy the code

If you look at the code, you get the setter for the type and you call it. The same is true for getValue, which I won’t read the source code for.

Set class case

Let’s look at an example:

public class Person {
    private String name;

    private List<String> friends;

    public Person(String name) {
        this.name = name;
        this.friends = new ArrayList<>(10);
    }

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getFriends(a) {
        return friends;
    }

    public void setFriends(int index, String friend) {
        this.friends.set(index, friend); }}Copy the code
@Test
void collectionTest(a) {
    Person person = new Person("Mary");
    person.getFriends().add("Johnson");

    MetaObject metaObject = SystemMetaObject.forObject(person);
    metaObject.setValue("friends[0]"."Peter");
    String friendName = (String) metaObject.getValue("friends[0]");
    System.out.println("The friend's name is " + friendName);
    // The friend's name is Peter
}
Copy the code

If it’s a collection class, how does the process go? This time we’ll use PropertyTokenizer:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  private String name;
  private final String indexedName;
  private String index;
  private final String children;

  public PropertyTokenizer(String fullname) {
    int delim = fullname.indexOf('. ');
    if (delim > -1) {
      name = fullname.substring(0, delim);
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim); }}public String getName(a) {
    return name;
  }

  public String getIndex(a) {
    return index;
  }

  public String getIndexedName(a) {
    return indexedName;
  }

  public String getChildren(a) {
    return children;
  }

  @Override
  public boolean hasNext(a) {
    returnchildren ! =null;
  }

  @Override
  public PropertyTokenizer next(a) {
    return new PropertyTokenizer(children);
  }

  @Override
  public void remove(a) {
    throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties."); }}Copy the code

Look at the constructor, and if it resolves to a collection class, extract the index. Then we go to the beanwrapper.setValue method:

@Override
public void set(PropertyTokenizer prop, Object value) {
  if(prop.getIndex() ! =null) {
    Object collection = resolveCollection(prop, object);
    setCollectionValue(prop, collection, value);
  } else{ setBeanProperty(prop, object, value); }}Copy the code

This time go prop.getIndex()! == null process:

protected Object resolveCollection(PropertyTokenizer prop, Object object) {
  if ("".equals(prop.getName())) {
    return object;
  } else {
    returnmetaObject.getValue(prop.getName()); }}Copy the code

Get the collection class, and then call setCollectionValue to assign it:

protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) {
  if (collection instanceof Map) {
    ((Map) collection).put(prop.getIndex(), value);
  } else {
    int i = Integer.parseInt(prop.getIndex());
    if (collection instanceof List) {
      ((List) collection).set(i, value);
    } else if (collection instanceof Object[]) {
      ((Object[]) collection)[i] = value;
    } else if (collection instanceof char[]) {((char[]) collection)[i] = (Character) value;
    } else if (collection instanceof boolean[]) {((boolean[]) collection)[i] = (Boolean) value;
    } else if (collection instanceof byte[]) {((byte[]) collection)[i] = (Byte) value;
    } else if (collection instanceof double[]) {((double[]) collection)[i] = (Double) value;
    } else if (collection instanceof float[]) {((float[]) collection)[i] = (Float) value;
    } else if (collection instanceof int[]) {((int[]) collection)[i] = (Integer) value;
    } else if (collection instanceof long[]) {((long[]) collection)[i] = (Long) value;
    } else if (collection instanceof short[]) {((short[]) collection)[i] = (Short) value;
    } else {
      throw new ReflectionException("The '" + prop.getName() + "' property of " + collection + " is not a List or Array."); }}}Copy the code

It looks like a lot of code, but all it does is determine what collection class it is and call the corresponding method to assign the value.

Case of nested classes

Finally, to get to the most complicated case, here’s another example:

public class Student {
    private Person person;

    private int id;

    public Student(int id, String name) {
        this.id = id;
        this.person = new Person(name);
    }

    public Person getPerson(a) {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public int getId(a) {
        return id;
    }

    public void setId(int id) {
        this.id = id; }}Copy the code
@Test
void nestTest(a) {
    Student student = new Student(11."Tom");

    MetaObject metaObject = SystemMetaObject.forObject(student);
    metaObject.setValue("person.name"."Peter");
    String name = (String) metaObject.getValue("person.name");
    System.out.println("The student name is " + name);
    // The student name is Peter
}
Copy the code

PropertyTokenizer now has children:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  private String name;
  private final String indexedName;
  private String index;
  private final String children;

  public PropertyTokenizer(String fullname) {
    int delim = fullname.indexOf('. ');
    if (delim > -1) {
      name = fullname.substring(0, delim);
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim); }}...public String getChildren(a) {
    return children;
  }

@Override
public boolean hasNext(a) {
  returnchildren ! =null;
}

  @Override
  public PropertyTokenizer next(a) {
    return newPropertyTokenizer(children); }}Copy the code

PropertyTokenizer,. The former property is assigned to name and the. Property to children, which is passed to children if traversed, generating a new PropertyTokenizer

And then there’s the recursive operation, which is a little bit convoluted, so get ready.

I’ll paste the setValue source code again:

public void setValue(String name, Object value) {
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      if (value == null) {
        // don't instantiate child path if value is null
        return;
      } else {
        metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
      }
    }
    metaValue.setValue(prop.getChildren(), value);
  } else{ objectWrapper.set(prop, value); }}Copy the code

MetaObject metaValue = metaObjectForProperty(prop.getIndexEdName ()); MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());

public MetaObject metaObjectForProperty(String name) {
  Object value = getValue(name);
  return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}
Copy the code

See that it calls getValue and then regenerates a MetaObject as a MetaObject. Note that the other three parameters remain the same except that the original object has changed. Take a look at getValue’s source code:

public Object getValue(String name) {
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      return null;
    } else {
      returnmetaValue.getValue(prop.getChildren()); }}else {
    returnobjectWrapper.get(prop); }}Copy the code

Objectwrapper.get (prop); objectwrapper.get (prop); . Metavalue.setvalue (prop.getChildren(), value); , the generated MetaObject calls setValue again, only this time with children, which means it will keep calling until there are no children in the direct prop, and the setValue method will go to objectwrapper. set(prop, Value).

Using person.name as an example, walk through the process to make things clearer. Enter setValue and PropertyTokenizer for parsing, and the result is as follows:

Because there are children, to go prop. HasNext process, enter the metaObjectForProperty, incoming parameters person to call getValue, finally get person objects, generate new MetaObject objects:

Based on the new MetaObject object, setValue is called again, passing in children, which is name, so:

Objectwrapper. set(prop, value); , completes the assignment.

The basic operation of MetaObject has been explained, and other operations are basically the same.