MyBatis Source Parsing-Reflection Module

1. Preface

The module is located in the org.apache.ibatis.reflection package, and MyBatis involves a large number of reflection operations in parameter processing, result mapping, and so on.Reflection in Java is powerful, but the code is complex and error-prone. To simplify the code associated with reflection operations, MyBatis provides a dedicated reflection module that further encapsulates common reflection operations and provides a simpler and more convenient reflection API.This section describes the implementation of the core code in this module.

2. Reflector

Reflector is the basis of the reflection module in MyBaits, where a lot of metadata is cached for reflection operations.The fields are as follows:

private final Class<?> type;  //Corresponding class type
private final String[] readablePropertyNames; //The name collection of the readable property, which is the property where the corresponding getter method exists
private final String[] writablePropertyNames; //A collection of names for writable attributes, which are attributes where the corresponding setter method exists
//The setter method corresponding to the property is recorded, key is the property name, value is the Invoker object, and it corresponds to the setter method
// Encapsulation of Method Objects
private final Map<String, Invoker> setMethods = new HashMap<>();
//Property corresponding getter method collection, key is property name, value is also Invoker object
private final Map<String, Invoker> getMethods = new HashMap<>();
//The parameter value type of the setter method corresponding to the property is recorded, key is the property name, value is the parameter type of the setter method
private final Map<String, Class<?>> setTypes = new HashMap<>();
//The parameter value type of the getter method corresponding to the property is recorded, key is the property name, value is the parameter type of the getter method
private final Map<String, Class<?>> getTypes = new HashMap<>();
//Default constructor recorded
private Constructor<?> defaultConstructor;
//A collection that records all property names
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

In Reflector's constructor, the specified Class object is parsed and populated with the following code:

public Reflector(Class<?> clazz) {
    type = clazz; //Initialize type field
    //Finding a default constructor (parameterless constructor) is implemented by iterating through all constructions with reflection
    addDefaultConstructor(clazz);
    //Handling getter methods in class es
    addGetMethods(clazz);
    //Processing setter methods in class es
    addSetMethods(clazz);
    //Processing fields without getter or setter methods
    addFields(clazz);
    // Initialize to an empty array
    readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    // Initialize to an empty array
    writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    //Initialize the caseInsensitivePropertyMap collection where property names in all uppercase formats are recorded
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

The Reflector.addGetMethods() method mainly resolves the getter method defined in the class, and the Reflector.addSetMethods() method mainly resolves the setter method defined in the class. The two methods are similar. Here we will take the Reflector.addGetMethods() method as an example, and the Reflector.addGetMethods() parse has the following three steps:

  1. First call the Reflector.getClassMethods() method to get the unique signature of the current class and all methods defined in the parent class and the corresponding Method object.

    private Method[] getClassMethods(Class<?> clazz) {
      //Method object used to record the unique signature corresponding to all methods defined in the specified class
      Map<String, Method> uniqueMethods = new HashMap<>();
      Class<?> currentClass = clazz;
      while (currentClass != null && currentClass != Object.class) {
        //Record all methods defined in the currentClass class
        addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
    
        // we also need to look for interface methods -
        // because the class may be abstract
        //Method for recording a definition in an interface
        Class<?>[] interfaces = currentClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
          addUniqueMethods(uniqueMethods, anInterface.getMethods());
        }
        //Get parent to continue while loop
        currentClass = currentClass.getSuperclass();
      }
    
      Collection<Method> methods = uniqueMethods.values();
      //Convert to methods Array Return
      return methods.toArray(new Method[0]);
    }

    In the Reflector.getClassMethods() method, a unique signature is generated for each method and recorded in the conflicting Setters collection, as follows:

    private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
      for (Method currentMethod : methods) {
        if (!currentMethod.isBridge()) {
          //Method signature rules derived from the getSignature() method: return value type#method name: list of parameter types.
          //The issuance signature obtained by the getSignature() method is globally unique and can be used as a unique identifier for this method.
          String signature = getSignature(currentMethod);
          // check to see if the method is already known
          // if it is known, then an extended class must have
          // overridden a method
          //Detects if the method has been added to subclasses [ignored if added]
          if (!uniqueMethods.containsKey(signature)) {
            //Record the relationship between the signature and the method
            uniqueMethods.put(signature, currentMethod);
          }
        }
      }
    }
  2. Then, according to the Java specification, find the getter method of the class from the Method array returned by the Reflector.getClassMethods() method and record it in the conflicting Setters collection.[The set is HashMap<String, <List<Method>>, key is the property name, and value is the setter set for the property.]

  3. When a subclass overrides the parent class's getter method and the return value changes, two methods with different signatures are generated in step [1]. The Reflector.addUniqueMethods() method is added to the conflicting Setters collection as two different methods, which is obviously not what we want, so we will call the Reflector.resolveSetterConflicts() method in step threeThis overridden method is handled, and the resulting getter method is recorded in the getMethods collection, and its return value type is populated in the getTypes collection.The Reflector.resolveSetterConflicts() method is implemented as follows:

    private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
      // Traversing the conflicting Getters collection
      for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
        Method winner = null;
        //Get Property Name
        String propName = entry.getKey();
        //Is it ambiguous
        boolean isAmbiguous = false;
        //
        for (Method candidate : entry.getValue()) {
          if (winner == null) {
            winner = candidate;
            continue;
          }
          //Get the method method method return value of the final selection
          Class<?> winnerType = winner.getReturnType();
          //Get Candidate Method Candidate Types
          Class<?> candidateType = candidate.getReturnType();
          //Return values are the same
          if (candidateType.equals(winnerType)) {
            //For not Boolean type
            if (!boolean.class.equals(candidateType)) {
              //Ambiguous type [ambiguous throw exception]
              isAmbiguous = true;
              break;
            } else if (candidate.getName().startsWith("is")) {
              //For boolean type
              //The current method return value is a subclass of the current most appropriate method return value
              winner = candidate;
            }
          } else if (candidateType.isAssignableFrom(winnerType)) {
            //The return value of the current most appropriate method is a subclass of the return value of the current method, and the current most appropriate method remains unchanged without doing anything.
            // OK getter type is descendant
          } else if (winnerType.isAssignableFrom(candidateType)) {  //The current return value is the return value of the currently most appropriate method
            winner = candidate;
          } else {
            //An ambiguous type throws an exception
            isAmbiguous = true;
            break;
          }
        }
        addGetMethod(propName, winner, isAmbiguous);
      }
    }

    The Reflector.addGetMethod() method populates the getMethods and getTypes collections with the following implementation code:

    private void addGetMethod(String name, Method method, boolean isAmbiguous) {
      //Throw exception handling for ambiguous methods
      MethodInvoker invoker = isAmbiguous
          ? new AmbiguousMethodInvoker(method, MessageFormat.format(
              "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
              name, method.getDeclaringClass().getName()))
          : new MethodInvoker(method);
      //Add the property name and the corresponding MethodInvoker object to the getMethods collection and parse the contents of the Invoker
      getMethods.put(name, invoker);
      //Gets the Type of the return value, which TypeParameterResolver will analyze later
      Type returnType = TypeParameterResolver.resolveReturnType(method, type);
      //Save the property name and the return value of its getter method in the getTypes collection, after the typeToClass() method
      getTypes.put(name, typeToClass(returnType));
    }

    After analyzing the three core steps of Reflector.addGetMethods(), let's look at the implementation code as follows:

    private void addGetMethods(Class<?> clazz) {
      //The conflicting Getters collection key is the property name and the value is the corresponding getter method collection, because the subclass may override the parent's getter method.
      //So there may be more than one getter method for the name of the same property
      Map<String, List<Method>> conflictingGetters = new HashMap<>();
      //Step 1: Get the specified class and the methods defined in the parent and interface
      Method[] methods = getClassMethods(clazz);
      //Step 2: Add the getter method to conflicting Getters following the javabean specification
      Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
        .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
      //Processing the getter method
      resolveGetterConflicts(conflictingGetters);
    

    The Reflector.addFields() method handles all the fields defined in the class and adds the processed field information to the setMethods collection, the setTypes collection, the getMethods collection, and the getTypes collection. The implementation code for the Reflector.addFileds() method is as follows:

    //Processing all fields defined in a class
    private void addFields(Class<?> clazz) {
      //Extract all fields defined in clazz
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
        if (!setMethods.containsKey(field.getName())) {
          // issue #379 - removed the check for final because JDK 1.5 allows
          // modification of final fields through reflection (JSR-133). (JGB)
          // pr #16 - final static can only be set by the classloader
          int modifiers = field.getModifiers();
          //Filtering methods for adjusting static and final modifications
          if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
            //Populate the setMethods and setTypes collections
            addSetField(field);
          }
        }
        //When a property with the same name is not packaged in a getMethods collection, it is recorded in the getMethods and getTypes collections
        if (!getMethods.containsKey(field.getName())) {
          //Fill getMethods and getTypes collections
          addGetField(field);
        }
      }
      if (clazz.getSuperclass() != null) {
        //Processing fields defined in parent class
        addFields(clazz.getSuperclass());
      }
    }

    Multiple get *() methods are provided in the Reflector to read metadata information from the above collection.

3. Invoker & GetFieldInvoker & SetFieldInvoker & AmbiguousMethodInvoker & MethodInvoker

When the add Methods() method and the add Field() method add elements to the above set, they encapsulate the corresponding Method object of the getter/setter method and the corresponding Field object of the field as Invoker objects.The Invoker interface is defined as follows:

public interface Invoker {
  //Call the value of the specified field or the specified method
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  //Returns the corresponding type of property
  Class<?> getType();
}

The invoker interface is implemented as follows:

GetFieldInvoker/SetFieldInvoker encapsulates a field object through a field field field, and invoke() for both is achieved by calling the Field.get()/set() method.MethodInvoker encapsulates the corresponding Method object through the Method field, and its invoke() method is implemented by calling the Method.invoke() method.

4. ReflectorFactory

The ReflectorFactory interface mainly implements the creation and caching of Reflector objects, which are defined as follows:

public interface ReflectorFactory {

  //Check if the ReflectorFactory object caches Reflector objects
  boolean isClassCacheEnabled();

  //Set whether Reflector objects are cached
  void setClassCacheEnabled(boolean classCacheEnabled);

  //Creates a Reflector object for the specified object
  Reflector findForClass(Class<?> type);
}

MyBatis only provides DefaultReflectorFactory as an implementation class for the ReflectorFactory interface. His relationship to Reflector is shown in the following figure:

DefaultReflectorFactory has the following field meanings:

//This field determines whether to cache Reflector objects
private boolean classCacheEnabled = true;
//Caching Reflector objects using the ConcurrentHashMap collection
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

DefaultReflectorFactory provides a findForClass() method to create a Reflector object for a specified Class and cache the Reflector object into the reflectorMap collection, as follows:

public Reflector findForClass(Class<?> type) {
  if (classCacheEnabled) { //Detect whether caching is turned on
   //cas put into cache
   return reflectorMap.computeIfAbsent(type, Reflector::new);
  } else {
   //Create without opening cache
   return new Reflector(type);
  }
}

In addition to using the DefaultReflectorFactory implementation provided by MyBatis, they can also configure custom ReflectorFactory implementation classes in mybatis-config.xml to extend these capabilities.(Extensions will be mentioned later when introducing the MyBatis initialization process.)

5. TypeParameterResolver

1. Type knowledge preparation

Before analyzing TypeParameterResolver, let's look at the Type interface. Type is the parent of all types, it has four interfaces and an implementation class, and the class diagram is shown in the diagram:

Class

Class: Represents the original type type type, Class object represents a class or interface in the JVM, and each Java class represents a Class object in the JVM.Class objects can be obtained in programs by "xxx.class", "object.getClass()" or Class.forName("class name").Arrays are also mapped as Class objects, and all arrays with the same element type and the same tangent dimension share a Class object.

ParameterizedType

ParameterizedType: Represents a parameterized type, such as List < String >, Map < Integer, String >, Service < User > which is generic.

GenericArrayType

GenericArrayType: Represents an array type and the component elements are ParameterizedType or TypeVariable.For example, List < String >[] or T []

TypeVariable

TypeVariable: Represents a type variable that reflects information before the JVM compiles the generic.For example, T in LIst is a type variable that needs to be converted to a specific type when compiling to work properly.

WildcardType

WildcardType: Represents a wildcard type, for example:? extends Number and? extends Integer.

2. Method Analysis

The basics of the Type interface are described above, so let's go back and analyze the TypeParameterResolver.In the analysis of Reflector, we see the shape of TypeParameterResolver, a tool class that provides a column of static methods to resolve fields, method return values, or the type of method parameters in a specified class.The calls between methods in TypeParameterResolver are roughly as follows: [Some of these recursive calls are not reflected and will be highlighted in subsequent analyses.]

The TypeParameterResolver resolveFieldTypes (), resolveReturnTypes(), and resolveParamTypes() methods resolve the field type, the method return value type, and the type of each parameter in the method parameter list, respectively.The three methods are similar.We will use resolveFieldTypes () to analyze.The TypeParameterResolve.resolveFieldTypes() method implements the following code:

public static Type resolveFieldType(Field field, Type srcType) {
  //Gets the declaration type of the field
  Type fieldType = field.getGenericType();
  //Gets the Class object of the class in which the field definition resides
  Class<?> declaringClass = field.getDeclaringClass();
  //Call the resolveType() method for subsequent processing
  return resolveType(fieldType, srcType, declaringClass);
}

All three of the above parsing methods call resolveType(), which chooses the appropriate method resolution based on the first parameter Type, that is, the method, the return value, or the type of the method return parameter.The second parameter of resolveType () represents the starting position for finding the field, return value, or method parameter.The third parameter represents the class in which the field and method reside.

The TypeParameterResolve.resolveType() method code is as follows:

private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
  if (type instanceof TypeVariable) {
    //Resolve TypeVariable Type
    return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
  } else if (type instanceof ParameterizedType) {
    //Resolving the ParameterizedType type
    return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
  } else if (type instanceof GenericArrayType) {
    //Resolve GenericArrayType Type
    return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
  } else {
    return type;
  }
}

The TypeParameterResolve.resolveParameterizedType() method code is as follows:

private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type srcType, Class<?> declaringClass) {
  // Gets the Class object corresponding to the original type in the parameterized type
  Class<?> rawType = (Class<?>) parameterizedType.getRawType();
  //Returns a type variable of a parameterized type
  Type[] typeArgs = parameterizedType.getActualTypeArguments();
  //Used to save parsed results
  Type[] args = new Type[typeArgs.length];
  for (int i = 0; i < typeArgs.length; i++) {
    if (typeArgs[i] instanceof TypeVariable) {
      //Resolve Type Variables
      args[i] = resolveTypeVar((TypeVariable<?>) typeArgs[i], srcType, declaringClass);
    } else if (typeArgs[i] instanceof ParameterizedType) {
      //If the ParameterizedType is nested, the resolveParameterizedType() method is called to process it
      args[i] = resolveParameterizedType((ParameterizedType) typeArgs[i], srcType, declaringClass);
    } else if (typeArgs[i] instanceof WildcardType) {
      //If WildcardType is nested, the resolveWildcardType() method is called to process it
      args[i] = resolveWildcardType((WildcardType) typeArgs[i], srcType, declaringClass);
    } else {
      args[i] = typeArgs[i];
    }
  }
  //Encapsulate the parsing result as a ParameterizedType implementation defined in TypeParameterResolver and return
  return new ParameterizedTypeImpl(rawType, null, args);
}

The TypeParameterResolve.resolveTypeVar() method is responsible for resolving TypeVariable.The specific implementation is as follows:

private static Type resolveTypeVar(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass) {
  Type result;
  Class<?> clazz;
  if (srcType instanceof Class) {
    clazz = (Class<?>) srcType;
  } else if (srcType instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) srcType;
    clazz = (Class<?>) parameterizedType.getRawType();
  } else {
    throw new IllegalArgumentException("The 2nd arg must be Class or ParameterizedType, but was: " + srcType.getClass());
  }

  if (clazz == declaringClass) {
    //Get Upper Bound
    Type[] bounds = typeVar.getBounds();
    if (bounds.length > 0) {
      return bounds[0];
    }
    return Object.class;
  }
  //Gets the declared parent type
  Type superclass = clazz.getGenericSuperclass();
  //Follow-up resolution by scanning parent class This is a recursive entry
  result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superclass);
  if (result != null) {
    return result;
  }
  // Get Interface
  Type[] superInterfaces = clazz.getGenericInterfaces();
  for (Type superInterface : superInterfaces) {
    //Follow-up parsing logic via scan interface with scan parent
    result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superInterface);
    if (result != null) {
      return result;
    }
  }
  //Returns Object.class if parsing succeeds in an integration structure
  return Object.class;
}

The scanSuperTypes() method is implemented as follows:

private static Type scanSuperTypes(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass, Class<?> clazz, Type superclass) {
  if (superclass instanceof ParameterizedType) {
    ParameterizedType parentAsType = (ParameterizedType) superclass;
    //Get the original type of the parent class
    Class<?> parentAsClass = (Class<?>) parentAsType.getRawType();
    TypeVariable<?>[] parentTypeVars = parentAsClass.getTypeParameters();
    if (srcType instanceof ParameterizedType) {
      //Convert parent parameterized types
      parentAsType = translateParentTypeVars((ParameterizedType) srcType, clazz, parentAsType);
    }
    if (declaringClass == parentAsClass) {
      for (int i = 0; i < parentTypeVars.length; i++) {
        if (typeVar.equals(parentTypeVars[i])) {
          return parentAsType.getActualTypeArguments()[i];
        }
      }
    }
    if (declaringClass.isAssignableFrom(parentAsClass)) {
      //Continue parsing the parent class until it resolves to the class that defines the field
      return resolveTypeVar(typeVar, parentAsType, declaringClass);
    }
  } else if (superclass instanceof Class && declaringClass.isAssignableFrom((Class<?>) superclass)) {
    //The declared parent class no longer contains type variables and is not the class that defines the field then continues to parse
    return resolveTypeVar(typeVar, superclass, declaringClass);
  }
  return null;
}

After analyzing the TypeParameterResolver.resolveTypeVar() and resolveParameterizedType() methods, look again at the resolveGenericArrayType() method, which is responsible for parsing variables of type GenericArrayType, which chooses the appropriate resolution *() method to parse based on the type of array elements, as follows:

private static Type resolveGenericArrayType(GenericArrayType genericArrayType, Type srcType, Class<?> declaringClass) {
  //Gets the type of array element
  Type componentType = genericArrayType.getGenericComponentType();
  Type resolvedComponentType = null;
  //Select the appropriate method for parsing based on the type of array element
  if (componentType instanceof TypeVariable) {
    resolvedComponentType = resolveTypeVar((TypeVariable<?>) componentType, srcType, declaringClass);
  } else if (componentType instanceof GenericArrayType) {
    //Recursively call the resolveGenericArrayType() method
    resolvedComponentType = resolveGenericArrayType((GenericArrayType) componentType, srcType, declaringClass);
  } else if (componentType instanceof ParameterizedType) {
    resolvedComponentType = resolveParameterizedType((ParameterizedType) componentType, srcType, declaringClass);
  }
  //Radical resolved array item type constructs return type
  if (resolvedComponentType instanceof Class) {
    return Array.newInstance((Class<?>) resolvedComponentType, 0).getClass();
  } else {
    return new GenericArrayTypeImpl(resolvedComponentType);
  }
}

Finally, let's analyze the TypeParameterResolver.resolveWildcardType() method.This method is responsible for resolving variables of type WildcardType.First, the upper and lower bounds of the WildcardType type are resolved, and then the WildcardTypeImpl object is constructed from the resolved results.The specific parsing process is similar to the resolution *() described above.

From the above analysis, we know that when there are complex inheritance relationships and generic types, TypeParameterResolver can help us parse the types of values returned by fields, methods, and methods.This is the basis of the Reflector class.

The MyBatis source code also provides TypeParameterResolverTest, a test class related to TypeParameterResolver, to test the functionality of TypeParameterResolver in many ways.We can refer to a better understanding of the functionality of TypeParameterResolver.

6. ObjectFactory

Many modules in MyBatis use the ObjectFactory interface, which provides an overload of several crate() methods through which you can create objects of a specified type.The ObjectFactory interface is defined as follows:

public interface ObjectFactory {
  //Set Configuration Information
  default void setProperties(Properties properties) {
    // NOP
  }
  //Create an object of a specified class through a parameterless constructor
  <T> T create(Class<T> type);
  //Select the appropriate constructor from the specified type to create the object based on the parameter list
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
  //Detects whether the specified type is a collection type, mainly dealing with java.util.Collection and its subclasses
  <T> boolean isCollection(Class<T> type);
}

DefaultObjectFactory is the only implementation of the ObjectFactory interface provided by MyBatis and is a reflection factory whose create() method is implemented by calling the instantiateClass () method.The DefaultObjectFactory.instantiateClass() method selects the appropriate constructor to instantiate the object based on the list of parameters passed in, as follows:

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
  try {
    Constructor<T> constructor;
    //Creating objects from parameterless constructors
    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;
        }
      }
    }
    //Find the constructor based on the specified parameters and instantiate the object
    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 {
        throw e;
      }
    }
  } 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);
  }
}

In addition to using the DefaultObjectFactory implementation provided by MyBatis, we can extend functionality by specifying custom ObjectFactory interface implementation classes in the mybatis-config.xml configuration file.

7. Property Toolset

Here we describe the three attribute tool classes used by the Reflection module, PropertyTokenizer, PropertyNamer, and ProperertyCopier.

PropertyTokenizer

Each field in PropertyTokenizer has the following meanings:

//Name of the current expression
private String name;
//Index name of current expression
private final String indexedName;
//Index Subscript
private String index;
//Subexpression
private final String children;

The incoming expression is analyzed in the constructor of PropertyTokenizer and the property field above is initialized as follows:

public PropertyTokenizer(String fullname) {
  //Find the location of'. '
  int delim = fullname.indexOf('.');
  if (delim > -1) {
    //Initialize name
    name = fullname.substring(0, delim);
    //Initialize children
    children = fullname.substring(delim + 1);
  } else {
    name = fullname;
    children = null;
  }
  //Initialize indexedName
  indexedName = name;
  delim = name.indexOf('[');
  if (delim > -1) {
    //Initialization index
    index = name.substring(delim + 1, name.length() - 1);
    name = name.substring(0, delim);
  }
}

PropertyTokenizer inherits the Iterator interface, which iterates over nested multilevel expressions.PropertyTokenizer.next() creates a new PropertyTokenizer object and parses the subexpression of the children field record.

PropertyNamer

PropertyNamer is another tool class that provides the following static methods to help complete the conversion of method names to property names, as well as various instrumentation operations.

public final class PropertyNamer {

  private PropertyNamer() {
    // Prevent Instantiation of Static Class
  }

  //This method converts the method name to an attribute
  public static String methodToProperty(String name) {
    if (name.startsWith("is")) {
      name = name.substring(2);
    } else if (name.startsWith("get") || name.startsWith("set")) {
      name = name.substring(3);
    } else {
      throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
    }

    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
    }

    return name;
  }

  // Detect whether the corresponding property name is
  public static boolean isProperty(String name) {
    return isGetter(name) || isSetter(name);
  }

  //Detect whether it is a getter method
  public static boolean isGetter(String name) {
    return (name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2);
  }

  //Detect whether it is a setter method
  public static boolean isSetter(String name) {
    return name.startsWith("set") && name.length() > 3;
  }
}

PropertyCopier

PropertyCopier is a tool class for copying attributes.The core method is the copyBeanProperties() method, which mainly implements the copy of attributes between two objects of the same type, as follows:

public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
  Class<?> parent = type;
  while (parent != null) {
    final Field[] fields = parent.getDeclaredFields();
    for (Field field : fields) {
      try {
        try {
          field.set(destinationBean, field.get(sourceBean));
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            field.setAccessible(true);
            //Setting properties in the sourceBean object to the destinationBean object
            field.set(destinationBean, field.get(sourceBean));
          } else {
            throw e;
          }
        }
      } catch (Exception e) {
        // Nothing useful to do, will only fail on final fields, which will be ignored.
      }
    }
    // Continue copying the fields defined in the parent class
    parent = parent.getSuperclass();
  }
}

8. MetaClass

MetaClass uses Reflector and Property Tokenizer together to parse complex attribute expressions and to obtain specified attribute information.Each field in MetaClass has the following meanings:

//Used to cache Reflector objects
private final ReflectorFactory reflectorFactory;
//When you create MetaClasyos, you specify a class that the Reflector object will use to record the metadata associated with that class
private final Reflector reflector;

The constructor of the MetaClass creates the corresponding Reflector object for the specified Class and initializes the MetaClass.reflector field with the following code:

//MetaClass is constructed using private ly modified
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
  this.reflectorFactory = reflectorFactory;
  //Create Reflector Object
  this.reflector = reflectorFactory.findForClass(type);
}

//Creating MetaClass objects using static methods
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  return new MetaClass(type, reflectorFactory);
}

More important in MetaClass is the findProperty() method, which is implemented through the MetaClass.buildProperty() method, which parses more complex property expressions through the PropertyTokenizer, as follows:

public String findProperty(String name) {
  //Delegate to buildProperty() method implementation
  StringBuilder prop = buildProperty(name, new StringBuilder());
  return prop.length() > 0 ? prop.toString() : null;
}
private StringBuilder buildProperty(String name, StringBuilder builder) {
    //Parsing attribute expression
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) { //Is there a subexpression
      //Find the property corresponding to PropertyTokenizer.name
      String propertyName = reflector.findPropertyName(prop.getName());
      if (propertyName != null) {
        //Append Property Name
        builder.append(propertyName);
        builder.append(".");
        //Create a MataClass object for this property
        MetaClass metaProp = metaClassForProperty(propertyName);
        //Recursively parse the PropertyTokenizer.children field and add the parsing results to the builder to save
        metaProp.buildProperty(prop.getChildren(), builder);
      }
    } else {
      //Recursive Exit
      String propertyName = reflector.findPropertyName(name);
      if (propertyName != null) {
        builder.append(propertyName);
      }
    }
    return builder;
 }
 public MetaClass metaClassForProperty(String name) {
    //Find the class corresponding to the specified property
    Class<?> propType = reflector.getGetterType(name);
    //Create a corresponding MetaClass object for a new attribute
    return MetaClass.forClass(propType, reflectorFactory);
 }

Note: The Meta.Class.findProperty() method only looks for attributes of the'. 'navigation and does not detect subscripts.

The MetaClass.hasGetter() method and the MetaClass.hasSetter() method are responsible for determining whether the attributes represented by the attribute expression have corresponding attributes. The logic of these two methods is similar. We take the hasGetter() method as an example to analyze. Both of these methods implementations will ultimately find the Reflector.getMethods collection or the Reflector.setMethods collection.Based on the Reeflector.addFields() method described earlier, a corresponding getFieldInvoker/setFieldInvoker object is added when a field does not have a corresponding getter/setter method, so when Reflector has access to a specified field, these two methods do not behave as if they were just getter/setter methods that directly judge attributes as implied by their method. Let's look at MetaClass.Haster()Method implementation code:

public boolean hasGetter(String name) {
  //Parsing attribute expression
  PropertyTokenizer prop = new PropertyTokenizer(name);
  //There are subexpressions to be processed
  if (prop.hasNext()) {
    //PropertyTokenizer.name specifies a property that has a getter method to handle subexpressions
    if (reflector.hasGetter(prop.getName())) {
      //metaClassForProperty(PropertyTokenizer) is an overload of the above metaClassForProperty(String) but the logic is very different
      MetaClass metaProp = metaClassForProperty(prop);
      //Recursive Entry
      return metaProp.hasGetter(prop.getChildren());
    } else {
      //Recursive Exit
      return false;
    }
  } else {
    //Recursive Exit
    return reflector.hasGetter(prop.getName());
  }
}

The bottom level of the MetaClass.metaClassForProperty(PropertyTokenizer) method calls the MetaClass.getGetterType(PropertyTokenizer) method to further process whether the PropertyTokenizer contains index information, as follows:

private MetaClass metaClassForProperty(PropertyTokenizer prop) {
  //Gets the type of attribute identified by the expression
  Class<?> propType = getGetterType(prop);
  return MetaClass.forClass(propType, reflectorFactory);
}

private Class<?> getGetterType(PropertyTokenizer prop) {
  //Get Attribute Type
  Class<?> type = reflector.getGetterType(prop.getName());
  //Whether the subscript is specified in the expression by using'[]', cut to the Collect subclass
  if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
    //Resolving the type of an attribute through the TypeParameterResolver tool class
    Type returnType = getGenericGetterType(prop.getName());
    //Processing for ParameterizedType, both for generic types
    if (returnType instanceof ParameterizedType) {
      //Get the actual type parameters
      Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
      if (actualTypeArguments != null && actualTypeArguments.length == 1) {
        //Types of generics
        returnType = actualTypeArguments[0];
        if (returnType instanceof Class) {
          type = (Class<?>) returnType;
        } else if (returnType instanceof ParameterizedType) {
          type = (Class<?>) ((ParameterizedType) returnType).getRawType();
        }
      }
    }
  }
  return type;
}

private Type getGenericGetterType(String propertyName) {
  try {
    //Depending on the type of invoker implementation class recorded in the Reflector.getMethods collection, decide whether to parse the getter method return value type or the field type
    Invoker invoker = reflector.getGetInvoker(propertyName);
    if (invoker instanceof MethodInvoker) {
      Field _method = MethodInvoker.class.getDeclaredField("method");
      _method.setAccessible(true);
      Method method = (Method) _method.get(invoker);
      return TypeParameterResolver.resolveReturnType(method, reflector.getType());
    } else if (invoker instanceof GetFieldInvoker) {
      Field _field = GetFieldInvoker.class.getDeclaredField("field");
      _field.setAccessible(true);
      Field field = (Field) _field.get(invoker);
      return TypeParameterResolver.resolveFieldType(field, reflector.getType());
    }
  } catch (NoSuchFieldException | IllegalAccessException ignored) {
  }
  return null;
}

Other get *() methods in MetaClass are relatively simple, and most rely directly on the implementation of Reflector's corresponding methods.Through the analysis of MetaClass, we understand the implementation principles of findProperty(), hasGetter(), hasSetter().

9. ObjectWrapper

ObjectWrapper, as its name implies, is a wrapper class for objects that handles object-level meta-information.The ObjectWrapper interface is a wrapper for objects, which abstracts the object's attribute information. It defines a series of methods for querying the attribute information and updating the attribute.

The ObjectWrapper interface is defined as follows:

public interface ObjectWrapper {
  //Get the value of the specified key or subscript if the getter method is called by a normal bean and if it is a collection
  Object get(PropertyTokenizer prop);
  //setter method is called if it is a normal bean. Set the value corresponding to the specified key or subscript if it is a collection
  void set(PropertyTokenizer prop, Object value);
  //Finds the attribute specified by the attribute expression, and the second parameter identifies whether the underline of the attribute expression is ignored
  String findProperty(String name, boolean useCamelCaseMapping);

  //Find a collection of names for readable properties
  String[] getGetterNames();
  //Find a collection of names for writable properties
  String[] getSetterNames();

  //The parameter type of the setter method that parses the expression to specify the property
  Class<?> getSetterType(String name);
  //The parameter type of the getter method that parses the expression to specify the property
  Class<?> getGetterType(String name);

  //Determines whether a property expression specifies a property has a getter method
  boolean hasSetter(String name);
  //Judging whether an attribute expression specifies a property has a setter method
  boolean hasGetter(String name);

  //Create the corresponding MetaObject object for the property specified in the property expression
  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

  //Whether the encapsulated object is of type Collect
  boolean isCollection();

  //Call the add() method of the Collection object
  void add(Object element);
  //Call addAll() method of Collection object
  <E> void addAll(List<E> element);
}

ObjectWrapperFactory is responsible for creating ObjectWrapper objects, as shown in the diagram:

DefaultObjectWrapperFactory implements the ObjectWrapperFactory interface, but the getWrapperFor() method it implements always throws exceptions, and the hasWrapperFor() method always returns false, so this implementation method is actually not available.But like ObjectFactory, we can extend it by configuring custom ObjectWrapperFactory implementation classes in mybatis-config.xml.

BaseWrapper is an abstract class that implements the ObjectWrapper interface. It encapsulates the MetaObject object and provides three commonly used methods for its subclasses.As shown in the diagram:

The BaseWrapper.resolveCollection() method calls the MetaObject.getValue() method, which parses the property expression and gets the specified property. The implementation of the MetaObject.getValue() method is described below.

The BaseWrapper.getCollectionValue() and setCollectionValue() methods parse the index information of the attribute expression and get or set the corresponding item.The two methods are similar.Here we will only analyze the getCollectionValue() method.

protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
  if (collection instanceof Map) {
    //If map type, index is key
    return ((Map) collection).get(prop.getIndex());
  } else {
    //If other type, index is subscript
    int i = Integer.parseInt(prop.getIndex());
    if (collection instanceof List) {
      return ((List) collection).get(i);
    } else if (collection instanceof Object[]) {
      return ((Object[]) collection)[i];
    } else if (collection instanceof char[]) {
      return ((char[]) collection)[i];
    } else if (collection instanceof boolean[]) {
      return ((boolean[]) collection)[i];
    } else if (collection instanceof byte[]) {
      return ((byte[]) collection)[i];
    } else if (collection instanceof double[]) {
      return ((double[]) collection)[i];
    } else if (collection instanceof float[]) {
      return ((float[]) collection)[i];
    } else if (collection instanceof int[]) {
      return ((int[]) collection)[i];
    } else if (collection instanceof long[]) {
      return ((long[]) collection)[i];
    } else if (collection instanceof short[]) {
      return ((short[]) collection)[i];
    } else {
      throw new ReflectionException("The '" + prop.getName() + "' property of " + collection + " is not a List or Array.");
    }
  }
}

BeanWrapper inherits the BaseWrapper Abstract class, which encapsulates a JavaBean object and its corresponding MetaClass object, as well as the corresponding MetaObject object inherited from BaseWrapper.

The BeanWrapper.get() method and the set() method get or set the corresponding property values based on the specified property expression, which are logically similar. Here we analyze them using the get() method with the following code:

public Object get(PropertyTokenizer prop) {
  //The presence of index information indicates that the name part of the attribute expression is of set type
  if (prop.getIndex() != null) {
    //Gets the specified collection properties in an object object object using the MetaObject.getValue() method
    Object collection = resolveCollection(prop, object);
    //Get collection elements
    return getCollectionValue(prop, collection);
  } else {
    //If no index information exists, the name part is a normal object, and the invoker related method is found and invoked to get the property
    return getBeanProperty(prop, object);
  }
}
private Object getBeanProperty(PropertyTokenizer prop, Object object) {
    try {
      //Find the corresponding getFieldInvoker or MethodInvoker in the Reflector.getMethods collection based on the property name
      Invoker method = metaClass.getGetInvoker(prop.getName());
      try {
        //Get property value
        return method.invoke(object, NO_ARGUMENTS);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
    }
  }

CollectionWrapper implements the ObjectWrapper interface, which encapsulates objects of type Collection < Object >, but most of its implementations throw an UnsupportedOperationException exception.

MapWrapper is another implementation class of BaseWrapper that encapsulates Map<String, Object>type objects, and once we understand the MetaObject and BeanWrapper implementations, it is easy to understand the MapWrapper implementation code.

10. MetaObject

MetaObject provides common functions such as getting/setting property values specified in an object and detecting getter/setter, but ObjectWrapper is only the last stop for these functions. We omitted the introduction of attribute expression parsing, which is implemented in MetaObject.

The meaning of a field in MetaObject is as follows:

//Original JavaBean Object
private final Object originalObject;
//Encapsulates the originalObject object
private final ObjectWrapper objectWrapper;
//Factory object responsible for instantiating originalObject
private final ObjectFactory objectFactory;
//Responsible for creating ObjectWrapper factory objects
private final ObjectWrapperFactory objectWrapperFactory;
//Factory object used to create and cache Reflector objects
private final ReflectorFactory reflectorFactory;

The construction method of the MetaObject creates the corresponding ObjectWrapper object based on the type of the original object passed in and the implementation of the ObjectFactory factory, coded as follows:

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
  //Initialize the above fields
  this.originalObject = object;
  this.objectFactory = objectFactory;
  this.objectWrapperFactory = objectWrapperFactory;
  this.reflectorFactory = reflectorFactory;

  //If the original object is already an ObjectWrapper object, use it directly
  if (object instanceof ObjectWrapper) {
    this.objectWrapper = (ObjectWrapper) object;
  } else if (objectWrapperFactory.hasWrapperFor(object)) {
    //If objectWrapperFactory can create a corresponding ObjectWrapper object for the original redemption, the objectWrapperFactory will be preferred.
    //DefaultObjectWrapperFactory.hasWrapperFor() always returns false, so users can customize the ObjectWrapperFactory implementation to extend it
    this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
  } else if (object instanceof Map) {
    //Create a MapWrapper object if the original object is of type map
    this.objectWrapper = new MapWrapper(this, (Map) object);
  } else if (object instanceof Collection) {
    //Create a CollectionWrapper object if the original object is of type Collection
    this.objectWrapper = new CollectionWrapper(this, (Collection) object);
  } else {
    //If the object is a regular JavaBean object, create a BeanWrapper object
    this.objectWrapper = new BeanWrapper(this, object);
  }
}
//Meta is constructed by private ly and can only be created by forObject() as a static method
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    if (object == null) {
      //Returns SystemMetaObject.NULL_META_OBJECT, a static object, if the original object is empty
      return SystemMetaObject.NULL_META_OBJECT;
    } else {
      return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
  }

Methods at the class level in MetaObject and ObjectWrapper, such as hasGetter(), hasSetter(), findProperty(), are implemented by calling the corresponding method of MetaClass directly.Other methods are object-level methods implemented in conjunction with ObjectWrapper, such as the MetaObject.getValue()/setValue() method, which is analyzed using the getValue() method with the following code:

public Object getValue(String name) {
  //Parsing attribute expression
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) { //Processing subexpressions
    //Create corresponding MetaObject objects based on properties defined after PropertyTokenizer parsing
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      return null;
    } else {
      //Recursive processing of subexpressions
      return metaValue.getValue(prop.getChildren());
    }
  } else {
    //Gets the specified property value through ObjectWrapper
    return objectWrapper.get(prop);
  }
}
public MetaObject metaObjectForProperty(String name) {
    //Gets the specified property
    Object value = getValue(name);
    //Create the corresponding MetaObject object for this property object
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
  }

The ObjectWrapper.instantiateProperty() method is essentially a create() method that calls the ObjectFactory interface (the default implementation is DefaultObjectFactory) to create an object and set it into the object to which it belongs, so let's look at the implementation code:

public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) {
  MetaObject metaValue;
  //Gets the parameter type of the setter method corresponding to the property
  Class<?> type = getSetterType(prop.getName());
  try {
    //Creating objects by reflection
    Object newObject = objectFactory.create(type);
    metaValue = MetaObject.forObject(newObject, metaObject.getObjectFactory(), metaObject.getObjectWrapperFactory(), metaObject.getReflectorFactory());
    //Set the property object created above to the corresponding property object collection
    set(prop, newObject);
  } catch (Exception e) {
    throw new ReflectionException("Cannot set value of property '" + name + "' because '" + name + "' is null and cannot be instantiated on instance of " + type.getName() + ". Cause:" + e.toString(), e);
  }
  return metaValue;
}

Once you understand how MetaObject and BeanWrapper deal with attribute values specified in attribute expressions recursively, the implementation of other methods is easy to understand.For example, methods such as getGetterType(), getSetterType(), hasGetter(), hasSetter(), etc., all process the attribute expression recursively, then call the corresponding method of MetaClass to implement it.

This article was created by Janker and licensed under CC BY 3.0 CN.It can be freely reproduced and quoted, but with the signature of the author and the citation of the article.If you upload it to the WeChat Public Number, add the Author Public Number 2-D at the end of the article.

Tags: Java Attribute Mybatis xml

Posted on Wed, 11 Mar 2020 12:43:13 -0400 by uknowho008