Beauty of Mybatis source code: 2.4.3. Reflector object of actual cache class reflection data

Reflector

>Reflector is an object defined in mybatis to describe the class definition information. It caches the type, readable / writable property, getter/setter method, constructor and other information of the specified object, and provides the entry to operate these properties or methods, which effectively simplifies the reflection operation on the methods and properties of the specified object.

There are many things involved in the definition and implementation of the Reflector object. Let's briefly understand the property definition of this class:

public class Reflector {
    /**
     * Types of encapsulated java objects
     */
    private final Class<!--?--> type;
    /**
     * {@link #type}Collection of readable property names for
     */
    private final String[] readablePropertyNames;
    /**
     * {@link #type}Writeable property name collection for
     */
    private final String[] writeablePropertyNames;
    /**
     * {@link #type}Mapping list of set method and actuator of
     */
    private final Map<string, invoker> setMethods = new HashMap&lt;&gt;();
    /**
     * {@link #type}get Mapping list of method sets and executors
     */
    private final Map<string, invoker> getMethods = new HashMap&lt;&gt;();
    /**
     * set Field type
     * Field name = & gt; field type
     */
    private final Map<string, class<?>&gt; setTypes = new HashMap&lt;&gt;();
    /**
     * get Field type
     * Field name = & gt; field type
     */
    private final Map<string, class<?>&gt; getTypes = new HashMap&lt;&gt;();
    /**
     * {@link #type}Default constructor for
     */
    private Constructor<!--?--> defaultConstructor;
    /**
     * {@link #type}The case insensitive property set of, all uppercase property name - & gt; property name
     */
    private Map<string, string> caseInsensitivePropertyMap = new HashMap&lt;&gt;();
    ...
  }

The function of these properties has been simply annotated, and their assignment operations and more detailed instructions will be given when parsing the construction method of the Reflector object.

The construction process of Reflector object is relatively complex, and a lot of extra operations are done during construction. Let's take a look at its construction and implementation as a whole, and then go deep into the internal dependent sub methods to understand the detailed implementation process:

public Reflector(Class<!--?--> clazz) {
    // Encapsulated JAVA type
    type = clazz;
    // Configure the default construction method
    addDefaultConstructor(clazz);
    // Configure a collection of methods starting with get/is
    addGetMethods(clazz);
    // Configure set method
    addSetMethods(clazz);
    // Add field collection
    addFields(clazz);

    // Configure a collection of readable field names
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    // Configure a collection of writable field names
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);

    // Convert to uppercase and put it into caseInsensitivePropertyMap for later searching
    for (String propName : readablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
}

After simply looking at the code of the construction method, we can probably guess the implementation of the internal method. The whole implementation depends on the reflection mechanism, obtaining the properties, methods and constructors of the java class passed in, and then processing and saving them for later use.

This thing is easy to understand, but the operation is a little tedious.

The whole tectonic process can be divided into two stages

  • The first stage is to parse the class <! --? -- > clazz object and obtain the basic information (properties and methods) in the class <! --? -- > clazz class.

    // Encapsulated JAVA type
    type = clazz;
    // Configure the default construction method
    addDefaultConstructor(clazz);
    // Configure a collection of methods starting with get/is
    addGetMethods(clazz);
    // Configure set method
    addSetMethods(clazz);
    // Add field collection
    addFields(clazz);
    
  • The second phase is the supplement of the first phase, which uses the data obtained in the first phase to improve other properties in the Reflector object.

    
    // Configure the readable field name collection
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    // Configure a collection of writable field names
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    
    // Convert to uppercase and put it into caseInsensitivePropertyMap for later searching
    for (String propName : readablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    

In the first phase, the Reflector will process the construction methods, getter/setter methods, and properties of the incoming clazz objects in sequence.

  • 1. Processing construction method

    Get the parameterless construction method of clazz and assign it to the defaultConstructor property.

  • 2. Handling getter/setter methods

    According to the definition of getter/setter methods in Java Bean, the effective methods are filtered out, and the corresponding property names of these methods are parsed in reverse direction. Then:

    • Wrap the method as an instance object of the Invoker interface and save it to the getMethods/setMethods collection
    • Remove the generics of the object returned by the method, convert it to the corresponding actual type, and save it in the getTypes/setTypes collection.
  • 3. Processing properties

    The processing of properties is a supplement to the results of the previous step. Because some properties in clazz may not have getter/setter methods defined, this part of properties may be missed in the previous step. In this step, for these properties:

    • The Reflector will generate instance objects of the Invoker interface for them to perform getter/setter operations based on the reflection, and save them to the getMethods/setMethods collection. >The function of the invoker interface is to unify the call mode based on the reflection processing method / property. The specific function will be given later.

    • Remove the generics of the property, convert it to the corresponding actual type, and save it in the getTypes/setTypes collection.

In the second phase, the Reflector saves the names of all the read / write properties in getMethods/setMethods to the readablePropertyNames/writeablePropertyNames collection, After that, all the readable / writable property names are capitalized in the caseInsensitivePropertyMap collection for later use.

After these two phases are completed, the construction of the Reflector object is completed.

After learning about what the construction method does, let's take a look at the specific implementation:

First of all, the Reflector must be a class passed in to save it as an instance variable for subsequent use:

// Cached JAVA type
type = clazz;

Next is the first step of the first stage. Get the default construction method of the class and assign it to the defaultConstructor property:

// Configure the default construction method
addDefaultConstructor(clazz);

The filtering mechanism of the default construction method is relatively simple, so we can get the nonparametric construction method.

private void addDefaultConstructor(Class<!--?--> clazz) {
       // Get all construction methods
       Constructor<!--?-->[] consts = clazz.getDeclaredConstructors();
       for (Constructor<!--?--> constructor : consts) {
           if (constructor.getParameterTypes().length == 0) {
               // Get nonparametric construction
               this.defaultConstructor = constructor;
           }
       }
   }

Then the second step of the first stage is to process getter and setter methods in turn. The processing operations of getter and setter methods are completed by addGetMethods and addSetMethods respectively

// Configure a collection of methods starting with get/is
addGetMethods(clazz);
// Configure set method
addSetMethods(clazz);

addGetMethods, which is used to process getter methods, involves many operations, including:

  • Gets all method definitions in the specified class and its parent class / interface, and filters out the valid getter methods
  • According to the Java Bean specification, use the method name of the getter method to get the corresponding property name of the method
  • Filter the unique getter method implementation for the property name obtained in the previous step according to the Java Bean specification
  • Convert the generic parameter definition of the return type of the getter method to the actual class definition, such as list < T > to list < string >
  • Save methods and properties
private void addGetMethods(Class<!--?--> cls) {
      // Used to resolve method conflicts. Overloading and overriding methods will result in one property corresponding to multiple methods
      Map<string, list<method>&gt; conflictingGetters = new HashMap&lt;&gt;();
      //  Get all methods of the class being processed
      Method[] methods = getClassMethods(cls);
      for (Method method : methods) {
          // Ignore all getter methods with input parameters
          if (method.getParameterTypes().length &gt; 0) {
              continue;
          }
          String name = method.getName();
          // Get name begins with get/is and is not a method of get/is.
          if ((name.startsWith("get") &amp;&amp; name.length() &gt; 3)
                  || (name.startsWith("is") &amp;&amp; name.length() &gt; 2)) {
              // Convert method to property name
              name = PropertyNamer.methodToProperty(name);
              // Record the method set corresponding to each attribute name
              addMethodConflict(conflictingGetters, name, method);
          }
      }
      // Handling method conflict
      resolveGetterConflicts(conflictingGetters);
}

The processing flow of the whole getter method is quite long. Considering the reading consistency, first understand the getClassMethods method which is responsible for obtaining all the method definitions of the specified class and its parent class / interface, which methods will be obtained and which methods will be sorted out.

getClassMethods method is actually right Class.getMethods() an enhanced implementation of method, which not only obtains the methods defined in the specified class, but also the methods defined in the parent class / interface implemented / inherited by the specified class.

/**
 * Class.getMethods()The enhanced implementation of the method will not only obtain the method of the specified class, but also the parent class and the method defined in the interface that the specified class implements / inherits.
 *
 * @param cls Specify class
 * @return All methods
 */
private Method[] getClassMethods(Class<!--?--> cls) {
    // Method set after de duplication
    Map<string, method> uniqueMethods = new HashMap&lt;&gt;();
    // java classes currently being processed
    Class<!--?--> currentClass = cls;
    while (currentClass != null &amp;&amp; currentClass != Object.class) {
        // currentClass is not empty and is not of type Object

        // Buffer all methods
        addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());

        // we also need to look for interface methods -
        // because the class may be abstract
        Class<!--?-->[] interfaces = currentClass.getInterfaces();
        for (Class<!--?--> anInterface : interfaces) {
            // Handle all its interfaces
            addUniqueMethods(uniqueMethods, anInterface.getMethods());
        }
        // Process parent
        currentClass = currentClass.getSuperclass();
    }
    Collection<method> methods = uniqueMethods.values();
    return methods.toArray(new Method[methods.size()]);
}

In this method, a uniqueMethods parameter of map < string, method > type is defined first. It is not difficult to guess from the attribute name. This attribute is used to store the de duplicated method set.

His key value is a String type signature generated according to the method return value type, method name and method input parameter type. Value stores the corresponding method itself.

We can first look at the calculation method of method signature. The implementation is relatively simple:

/**
 * Generate signature for specified method
 *
 * @param method method
 * @return Method signature
 */
private String getSignature(Method method) {
    StringBuilder sb = new StringBuilder();
    // Process return type
    Class<!--?--> returnType = method.getReturnType();
    if (returnType != null) {
        sb.append(returnType.getName()).append('#');
    }
    // Processing method name
    sb.append(method.getName());
    // Processing method input
    Class<!--?-->[] parameters = method.getParameterTypes();
    for (int i = 0; i &lt; parameters.length; i++) {
        if (i == 0) {
            sb.append(':');
        } else {
            sb.append(',');
        }
        sb.append(parameters[i].getName());
    }
    return sb.toString();
}

Because the signature of the method is generated according to the return value, name and input parameter type of the method, if the subclass overrides the method of the parent, the signature of the subclass method and the parent method will be the same. For this scenario, mybatis will retain the method implementation of the subclass.

After defining the uniqueMethods attribute, mybaits will recursively process the current class, its parent class and interface, take out all the methods declared in it, calculate the method signature, and store it in the uniqueMethods collection.

!! It should be noted that if the currently processed class has a parent class definition and the parent class definition is not an Object, Then the whole step of getting the current class and its parent class and interface, taking out all the methods declared therein, calculating the method signature, and storing it in the uniqueMethods collection will be executed recursively until the parent class being processed is declared to be empty, or the parent class of the current class being processed is declared to be Object.

private void addUniqueMethods(Map<string, method> uniqueMethods, Method[] methods) {
    for (Method currentMethod : methods) {
        // Skip bridge method
        if (!currentMethod.isBridge()) {
            // Get method signature
            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
            // If the method does not exist, save it
            if (!uniqueMethods.containsKey(signature)) {
                uniqueMethods.put(signature, currentMethod);
            }
        }
    }
}

In the process of saving the method, mybatis will skip the processing of the bridge method automatically generated by JDK, and generate a signature for the method that meets the requirements (the calculation rule of signature has been mentioned earlier). Then save the method without the signature of the method in uniqueMethods. > Point to understand the bridging method of JDK

In this way, we can get all the methods defined in the specified class, its parent class and interface through the getClassMethods method method. All the methods here refer to the collection of methods after eliminating the methods rewritten by the subclass.

After learning which methods getClassMethods will get for a class, let's continue to look at the addGetMethods method.

In addGetMethods method, a confirmatinggetters property of map < string, list < method > & gt; type is defined. The key value of confirmatinggetters is used to store the property name corresponding to getter method, and the value value is used to store the getter method set corresponding to the property name.

// Used to resolve method conflicts. Overloading and overriding methods will result in one property corresponding to multiple methods
Map<string, list<method>&gt; conflictingGetters = new HashMap&lt;&gt;();

>Why does a property name correspond to multiple getter methods? >This is because in Reflector, the generation of property names is only based on method names, >Because the overriding and overloading of a method will lead to multiple methods with the same name, and then a scenario in which an attribute name corresponds to multiple methods will appear, >At the same time, according to the specification of Java Bean, there may be two getter methods for boolean type attributes, i.e. get/is, and there will be a scenario where one attribute name corresponds to multiple methods. >> Click to learn about the JavaBean specification

After declaring the conflictingGetters property, mybatis calls the getClassMethods method to get the collection of methods to be processed from the current java object.

// Gets all methods that get methods in the parent class / interface that the specified class implements / inherits
Method[] methods = getClassMethods(cls);

After that, the Reflector will process each method in the collection in turn. Because we need to process getter type methods here, we will ignore the methods with input parameters. At the same time, according to the Java Bean specification, we only process the methods that start with get/is and the method name is not completely get/is.

>Because the method with input parameters is ignored, theoretically speaking, the scenario that multiple overloaded methods appear in the method set at the same time is excluded. Therefore, in the method set, each method name only corresponds to one method.

for (Method method : methods) {
   // Ignore all getter methods with input parameters
   if (method.getParameterTypes().length &gt; 0) {
       continue;
   }
   String name = method.getName();
   // Get name begins with get/is and is not a method of get/is.
   if ((name.startsWith("get") &amp;&amp; name.length() &gt; 3)
           || (name.startsWith("is") &amp;&amp; name.length() &gt; 2)) {
       // Convert method to property name
       name = PropertyNamer.methodToProperty(name);
       // Record the method set corresponding to each attribute name, and generate the set for solving method conflicts
       addMethodConflict(conflictingGetters, name, method);
   }
}

Specifically, to deal with the details of each method, Reflector first uses a class called PropertyNamer to reverse the property name of the method name according to the Java Bean specification, and then calls the addMethodConflict method to save the mapping relationship between the attribute name and the method. We will talk about the PropertyNamer class later.

The function of the addMethodConflict method is to fill in the mapping relationship between the property name and the getter method in the conflictingGetters set defined in the front.

private void addMethodConflict(Map<string, list<method>&gt; conflictingMethods, String name, Method method) {
      // If there is no method list corresponding to the specified property name, create a new method list
      List<method> list = conflictingMethods.computeIfAbsent(name, k -&gt; new ArrayList&lt;&gt;());
      // Save method
      list.add(method);
  }

After the above operations are completed, the Reflector will call the resolveGetterConflicts method to process the conflictingMethods collection, and select an effective and optimal getter method that best conforms to the Java Bean specification for each attribute:

// Handling method conflict
resolveGetterConflicts(conflictingGetters);
private void resolveGetterConflicts(Map<string, list<method>&gt; conflictingGetters) {
    // Expect to get the final candidate method from the overloaded method
    for (Entry<string, list<method>&gt; entry : conflictingGetters.entrySet()) {
        // Save the final method
        Method winner = null;
        // Current property name
        String propName = entry.getKey();
        for (Method candidate : entry.getValue()) {
            // Handle every effective method
            if (winner == null) {
                // Processing will be skipped after the first method is assigned
                winner = candidate;
                continue;
            }
            // Get the return type of the currently winning method
            Class<!--?--> winnerType = winner.getReturnType();
            // Get the return type of the current candidate method
            Class<!--?--> candidateType = candidate.getReturnType();
            if (candidateType.equals(winnerType)) {
                // The return types of the two methods are the same, but not boolean
                if (!boolean.class.equals(candidateType)) {
                    // This is because in the javabean specification, it can have both 'get * ()' and 'is * ()' methods.
                    // Properties of non boolean type have two getter methods at the same time,
                    // For example, the name attribute contains both 'getName()' and 'isName()'.
                    throw new ReflectionException(
                            "Illegal overloaded getter method with ambiguous type for property "
                                    + propName + " in class " + winner.getDeclaringClass()
                                    + ". This breaks the JavaBeans specification and can cause unpredictable results.");
                } else if (candidate.getName().startsWith("is")) {
                    // For properties of boolean type, if there are 'get * ()' and 'is * ()' methods at the same time, the is starting method is preferred.
                    winner = candidate;
                }
            } else if (candidateType.isAssignableFrom(winnerType)) {
                // The return value of the candidate method is a super class of the return value of the current winning method, which does not need to be processed and uses a small range of subclasses.
                // OK getter type is descendant
            } else if (winnerType.isAssignableFrom(candidateType)) {
                // The return value of the candidate method is a subclass of the return value of the current victory method, exchanging the victory identity
                winner = candidate;
            } else {
                // The return value type of the overloaded method has nothing to do with it.
                throw new ReflectionException(
                        "Illegal overloaded getter method with ambiguous type for property "
                                + propName + " in class " + winner.getDeclaringClass()
                                + ". This breaks the JavaBeans specification and can cause unpredictable results.");
            }
        }
        // Save getter method
        addGetMethod(propName, winner);
    }
}

The above method looks very long, but it is not complicated. The calculation rules of the optimal getter method are as follows:

  • If the return types of multiple methods corresponding to a property are the same, it means that the method has two methods at the beginning of get and is at the same time. However, except for the method with the beginning of is is is preferred for the property of boolean type, two getter methods are not allowed for the properties of other types.

  • If the return types of multiple methods corresponding to the attribute are inconsistent, there must be inheritance relationship between the return values of multiple methods. In this case, the method with more accurate return value type should be selected as the optimal method.

After we get the optimal getter method corresponding to the property name, the Reflector will call the addGetMethod method method to complete the operation of saving the property and method.

// Save getter method
addGetMethod(propName, winner);
private void addGetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
        // Save to getter method
        getMethods.put(name, new MethodInvoker(method));
        // Get the return type of the method (remove the generic conversion to the actual type)
        Type returnType = TypeParameterResolver.resolveReturnType(method, type);
        // Save the relationship between attributes and their corresponding java types
        getTypes.put(name, typeToClass(returnType));
    }
}

Only legal property names and methods are processed in the addGetMethod method. The isValidPropertyName method is used to determine whether the property names are legal

/**
 * Verify that it is a valid property name
 *
 * @param name Property name
 */
private boolean isValidPropertyName(String name) {
    return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
}

For the data with legal property name, mybatis will package the best getter method obtained previously as the Invoker instance of MethodInvoker type, Save to the getMethods collection.

// Save to getter method
getMethods.put(name, new MethodInvoker(method));

The generics of the return value Type of the method are converted to the actual Type by the resolveReturnType method of TypeParameterResolver, and then the actual Type obtained is converted from the Type to the Class Type by the typeToClass method and then put into the getTypes collection.

// Get the return type of the method (remove the generic conversion to the actual type)
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
// Save the relationship between attributes and their corresponding java types
getTypes.put(name, typeToClass(returnType));

>The resolution of methodinvoker and TypeParameterResolver will be given later.

>Now we just need to understand that the function of MethodInvoker is to simplify the reflection operation of methods. The function of resolveReturnType method of TypeParameterResolver is to remove the generic definition of the return value of methods and convert it to the actual type.

In the addGetMethod method method, we call the typeToClass method:

// Save the relationship between attributes and their corresponding java types
getTypes.put(name, typeToClass(returnType));

The typeToClass method is used to convert a Type type to a Class Type.

private Class<!--?--> typeToClass(Type src) {
    Class<!--?--> result = null;
    if (src instanceof Class) {
        // Common object
        result = (Class<!--?-->) src;
    } else if (src instanceof ParameterizedType) {
        // Handle parameterized generics and get the type of generics, for example: Map < K, V > to get map.
        result = (Class<!--?-->) ((ParameterizedType) src).getRawType();
    } else if (src instanceof GenericArrayType) {
        // Gets the type of an element in a generic array
        Type componentType = ((GenericArrayType) src).getGenericComponentType();
        if (componentType instanceof Class) {
            // Element type in generic array is Class
            result = Array.newInstance((Class<!--?-->) componentType, 0).getClass();
        } else {
            // Element type or generic in generic array
            // Recursive processing
            Class<!--?--> componentClass = typeToClass(componentType);
            result = Array.newInstance(componentClass, 0).getClass();
        }
    }

    if (result == null) {
        // src is a TypeVariable or WildcardType
        result = Object.class;
    }
    return result;
}

The logic of the whole parsing operation is relatively simple. For the src attribute of Type, its processing logic is as follows:

  • If src is a normal Class, return it directly,
  • If src is a parameterized type, get the type of the parameterized generic. For example, if the definition of generic K is List < k >, then List will be obtained.
  • If the Type is a genericarraytype (generic array), get the generic definition of the array element. If it is not a generic Type, return the element Type directly. If it is a generic Type, call the typeToClass method recursively.
  • If src does not satisfy any of the above conditions, return Object.class .

At this point, the processing of getter method by Reflector is completed.

Next, Reflector will continue to process the setter methods in the specified class and add the corresponding properties to setMethods and setTypes.

The processing of the Reflector for the setter method is roughly the same as that of the getter method, and its implementation depends on the addSetMethods method.

// Configure set method
addSetMethods(clazz);

In the addSetMethods method, the Reflector also defines a collection for saving property names and their corresponding methods, but this time it is named conflictingSetters, and it also calls the getClassMethods method method to get all the methods that need to be processed.

// Set of setter methods conflicting due to overload
Map<string, list<method>&gt; conflictingSetters = new HashMap&lt;&gt;();
// Get all the methods that need to be processed
Method[] methods = getClassMethods(cls);

After that, the Reflector will process each method in the collection in turn. Because we need to process the method of type setter here, we need to have only one input parameter definition for the method. At the same time, according to the Java Bean specification, we only process the method that starts with set and the method name is not completely set.

To deal with the details of each method, Reflector still uses PropertyNamer to reverse the corresponding name of the attribute according to the Java Bean specification, and then calls the addMethodConflict method to preserve the mapping relationship between the attribute name and the method.

Here, the function of the addMethodConflict method is to fill the previously defined conflictingSetters collection with the mapping relationship between the property name and the setter method.

// Set of setter methods conflicting due to overload
Map<string, list<method>&gt; conflictingSetters = new HashMap&lt;&gt;();
// Get all the methods that need to be processed
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
    String name = method.getName();
    // Starts with set and has only one input parameter
    if (name.startsWith("set") &amp;&amp; name.length() &gt; 3) {
        if (method.getParameterTypes().length == 1) {
            // Get the corresponding property name
            name = PropertyNamer.methodToProperty(name);
            // Save method
            addMethodConflict(conflictingSetters, name, method);
        }
    }
}
// Handling setter method conflicts
resolveSetterConflicts(conflictingSetters);

After the above operations are completed, the Reflector will call the resolveSetterConflicts method to process the conflictingSetters collection, and select an effective and optimal setter method that best conforms to the Java Bean specification for each property:

The resolveSetterConflicts method is quite different from the resolveGetterConflicts method in implementation.

private void resolveSetterConflicts(Map<string, list<method>&gt; conflictingSetters) {
     for (String propName : conflictingSetters.keySet()) {
         // Process the set of setter methods corresponding to each property

         // Get the setter method set corresponding to the property
         List<method> setters = conflictingSetters.get(propName);
         // Try to get the type corresponding to the property from getTypes
         Class<!--?--> getterType = getTypes.get(propName);
         // Final match method
         Method match = null;
         ReflectionException exception = null;
         for (Method setter : setters) {
             // Get input parameter type
             Class<!--?--> paramType = setter.getParameterTypes()[0];
             if (paramType.equals(getterType)) {
                 // should be the best match
                 // Method input type is the same as attribute type, so it is the best choice
                 match = setter;
                 break;
             }
             if (exception == null) {
                 try {
                     // Try to get the better setter method of the two methods
                     match = pickBetterSetter(match, setter, propName);
                 } catch (ReflectionException e) {
                     // there could still be the 'best match'
                     match = null;
                     exception = e;
                 }
             }
         }
         if (match == null) {
             throw exception;
         } else {
             // Save setter method
             addSetMethod(propName, match);
         }
     }
 }

In the resolveSetterConflicts method, each attribute definition is processed in turn:

For specific property processing operations, Reflector will first obtain the property corresponding setter method collection, and try to select an optimal setter method from this setter method collection for subsequent saving operations.

In the process of filtering the optimal setter method, the Reflector will first try to get the java type corresponding to the property from the getTypes collection (if the property has a getter method, its java type will be added to the getTypes collection in the addGetMethods method method in the previous step).

If we get the corresponding java type, we will use this java type to compare with the input parameters of each setter method. If the two are the same, then this setter method is the best setter method we need.

If all the input parameters of the setter method cannot match the java type, the Reflector will give all the setter methods to the pickBetterSetter method for pairwise comparison, and select the method with more accurate input parameters as the best setter method.

If there is no relationship between the input parameter types of the two setter methods during the comparison, the Reflector cannot select the best setter method, so an exception will be triggered.

/**
 * The function of this method is to choose a better setter method definition for the specified property.
 * setter Method selection depends on the relationship between the input parameters of the two setter methods. If the input parameter types of the two methods are not associated, an exception will be caused.
 * Otherwise, the two input parameter types will be obtained with relatively more precise methods.
 *
 * @param setter1  First setter method
 * @param setter2  Second setter method
 * @param property Property name
 * @return Best setter method
 */
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    if (setter1 == null) {
        return setter2;
    }
    // Get the input parameters of the two methods respectively
    Class<!--?--> paramType1 = setter1.getParameterTypes()[0];
    Class<!--?--> paramType2 = setter2.getParameterTypes()[0];

    // Get the more precise one of the two classes
    if (paramType1.isAssignableFrom(paramType2)) {
        return setter2;
    } else if (paramType2.isAssignableFrom(paramType1)) {
        return setter1;
    }
    throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
            + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
            + paramType2.getName() + "'.");
}

When we get the best setter method, the Reflector will delegate the addSetMethod method method to save the properties and best setter methods to setMethods and setTypes.

private void addSetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
        // Save the relationship between property and setter method executor
        setMethods.put(name, new MethodInvoker(method));
        // Get method actual input parameter type (remove generics to actual type)
        Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
        // Save property and its setter method input parameter type relationship
        setTypes.put(name, typeToClass(paramTypes[0]));
    }
}

The addSetMethod method is similar to the addGetMethod method in processing. It also relies on the isValidPropertyName method to filter out the legal property names to be processed.

For the data with legal property names, the addSetMethod method will also wrap the optimal setter method obtained previously as an Invoker instance of MethodInvoker type, Then save to the setMethods collection.

// Save the relationship between property and setter method executor
setMethods.put(name, new MethodInvoker(method));

The difference is that for generic processing, addSetMethod is The generics of the method input parameter Type are converted to the actual Type through the resolveParamTypes method of TypeParameterResolver, and then the acquired Type is converted from Type to Class Type through typeToClass and put into the setTypes collection.

Here, the setter method processing is completed. Next, step 3 of the first stage deals with the property definition of the specified class.

The processing of the specified class properties by the Reflector is done by the addFields method.

// Add field collection
addFields(clazz);

The addFields method is actually a supplement to the addGetMethods method and addSetMethods method. Because some properties in clazz may not have a getter/setter method defined, the processing of these properties may be missed in the addGetMethods method and addSetMethods methods methods methods.

private void addFields(Class<!--?--> clazz) {
    // Get all field definitions
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        // Handle only properties without getter/setter methods
        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();
            if (!(Modifier.isFinal(modifiers) &amp;&amp; Modifier.isStatic(modifiers))) {
                // Nonstatic constant
                addSetField(field);
            }
        }
        if (!getMethods.containsKey(field.getName())) {
            addGetField(field);
        }
    }
    if (clazz.getSuperclass() != null) {
        // Recursively handle parent classes
        addFields(clazz.getSuperclass());
    }
}

The addFields method is used to handle these missing properties:

In the specific implementation of this method, the Reflector will first obtain all the property definitions of clazz:

// Get all field definitions
Field[] fields = clazz.getDeclaredFields();

Then, for each specific property, the setter method and getter method are processed respectively.

We said before that addFields method is a supplement to the implementation of addGetMethods method and addSetMethods method, so when processing the setter method corresponding to a property, it is required that the property is not saved in the setMethods collection At the same time, it is required that the definition of the property cannot be a static constant, because the assignment of the static constant can only be completed in the class loader, and cannot be modified by reflection. For properties that meet the requirements, the Reflector will give them to the addSetField method to complete subsequent processing.

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();
   if (!(Modifier.isFinal(modifiers) &amp;&amp; Modifier.isStatic(modifiers))) {
       // Handling non static constants
       addSetField(field);
   }
}

When processing the getter method of a property, for the same reason, it is required that the property is not saved in the getMethods collection. For the qualified property, the Reflector will hand it to the addGetField method to complete the subsequent processing.

if (!getMethods.containsKey(field.getName())) {
   addGetField(field);
}

The implementation of the addGetField method is similar to that of the addSetMethods method. They will first use the isValidPropertyName method to verify the validity of property names. For legal properties:

  • The addGetField method wraps the property as an Invoker instance of type GetFieldInvoker and stores it in the getMethods collection,
  • The addSetMethods method wraps the property as an Invoker instance of type SetFieldInvoker and stores it in the setMethods collection

After that, both methods will convert the generics in the attribute definition to the actual Type through the resolveFieldType of TypeParameterResolver, and store them in the setTypes or getTypes collection after converting them from the Type type to the Class Type through the typeToClass method.

private void addSetField(Field field) {
    if (isValidPropertyName(field.getName())) {
        // setter method of configuration field
        setMethods.put(field.getName(), new SetFieldInvoker(field));
        // Get the field type
        Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
        // Save the relationship between field name and field type
        setTypes.put(field.getName(), typeToClass(fieldType));
    }
}
private void addGetField(Field field) {
    if (isValidPropertyName(field.getName())) {
        // Configuration field getter method
        getMethods.put(field.getName(), new GetFieldInvoker(field));
        // Get the type of the field
        Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
        // Save the relationship between field name and field type
        getTypes.put(field.getName(), typeToClass(fieldType));
    }

After finishing the processing operations of all properties in the current class, the Reflector will obtain the parent class of the current clazz as a parameter and continue to recursively call the addFields method to complete the processing operations defined by the properties in the parent class.

After all the properties in the parent class are processed, the first stage of the Reflector object construction method (parsing the class <! --? -- > clazz object, getting the basic information (properties and methods) in the class <! --? -- > clazz class) is completed.

The next step is to rely on the data obtained by the front end to complete the assignment of other properties in the Reflector object.

In the second phase, the Reflector takes the names of all the readable properties in the getMethods collection and saves them to the readablePropertyNames collection:

// Configure a collection of readable field names
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);

Get the names of all writeable properties in the setMethods collection and save them to the writeablePropertyNames collection:

// Configure a collection of writable field names
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);

After that, all property names are converted to uppercase and saved in the caseInsensitivePropertyMap collection:

// Convert to uppercase and put it into caseInsensitivePropertyMap for later searching
 for (String propName : readablePropertyNames) {
     caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
 }
 for (String propName : writeablePropertyNames) {
     caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
 }

After the assignment of the above properties, the construction method of the Reflector object is completed.

When parsing the constructor of a Reflector object, we mentioned that its implementation depends on the Invoker and its implementation class.

Pay attention to me and learn more together

</method></string,></string,></string,></k></k,v></string,></string,></method></string,></string,></string,></string,></string,method></method></string,></string,></string></t></string,></string,></string,></string,></string,>

Tags: Programming Java Attribute Mybatis JDK

Posted on Fri, 05 Jun 2020 02:33:59 -0400 by Germaris