Beauty of Mybatis source code: 2.6. Analyze the typeAliases element to complete the registration of type aliases

Resolve the typeAliases element to complete the registration of type aliases

> Click to see the usage of the typeAliases element

The typeAliases element is used to complete the configuration of type alias mapping in mybatis. We have learned a little about the type alias mechanism of mybatis. Its function is to provide a shorter name for the specified JAVA type, so as to simplify the redundancy brought by using the fully qualified name, and to simplify an optimized operation of the amount of code when we use mybatis.

To configure a custom alias in Mybatis, you need to use typealiases. The DTD of typealiases is defined as follows:

<!--ELEMENT typeAliases (typeAlias*,package*)-->

<!--ELEMENT typeAlias EMPTY-->
<!--ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
-->

<!--ELEMENT package EMPTY-->
<!--ATTLIST package
name CDATA #REQUIRED
-->

According to the DTD definition of typealiases, zero or more typeAlias/package nodes are allowed under typealiases, and typeAlias and package are not allowed to contain other child nodes.

Among them:

  • The type alias node is used to register a single alias mapping relationship. It has two parameters that can be filled in. The type parameter points to the fully qualified name of a java type, which is required. The alias parameter indicates the alias of the java object, which is not required. By default, it is obtained by using the class ×× getsimplename() method of the java class
  • Package is usually used to batch register alias mapping relationships. It has only one required parameter name, which points to a java package name. All the packages conform to the rules (the default is Object.class All classes are registered.

The typeAliasesElement method of XmlConfigBuilder is also simple to parse these two types of nodes:

/**
 * Parsing configuration typeAliases node
 *
 * @param parent typeAliases node
 */
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // Batch resolve aliases according to package. The default value of aliases is SimpleName of entity class
                String typeAliasPackage = child.getStringAttribute("name");
                // Register alias mapping to alias registry
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // Process typeAlias configuration, get alias and type and perform registration

                // alias
                String alias = child.getStringAttribute("alias");
                // java type
                String type = child.getStringAttribute("type");

                try {
                    // Get java type by reflection
                    Class<!--?--> clazz = Resources.classForName(type);
                    if (alias == null) {
                        // Alias not specified
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        // assign an alias
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

Let's first look at the parsing process of the typeAlias node, and then look at the package node.

XmlConfigBuilder will obtain the value of alias and type parameter of typeAlias node in turn, convert type to actual java type through reflection, and then transfer the operation of alias registration to typeAliasRegistry object,

If the user specifies the value of the alias parameter, then call the resolveAlias(String,Class) method of TypeAliasRegistry to complete the alias registration, which we have learned before.

If the user does not specify the value of alias parameter, the work of registering alias will be completed by the resolveAlias(Class) method of TypeAliasRegistry:

/**
 * Register an alias of the specified type in the alias registry
 * <p>
 * In the scenario without annotation, the short name of the instance type will be used as an alias after lowercase
 * </p><p>
 * If the {@ link Alias} annotation is specified, use the name specified by the annotation as the alias
 *
 * @param type Specify type
 */
public void registerAlias(Class<!--?--> type) {
    // Class name is the simple name of the class by default
    String alias = type.getSimpleName();
    // Handling alias configuration in annotations
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        // Use annotation values
        alias = aliasAnnotation.value();
    }
    // Register class alias
    registerAlias(alias, type);
}

In the resolveAlias(Class) method, the Alias annotation specified on the type is preferred as the Alias. If there is no Alias annotation, the short name of the type is used as the Alias.

After obtaining the alias of the specified type, the specific implementation is also given to the resolveAlias(String,Class) method

Alias annotation is relatively simple. Its function is to annotate aliases for specified types.

/**
 * Provides an alias for the specified class
 *
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Alias {
    /**
     * alias
     * @return alias
     */
    String value();
}

After watching the parsing of the typeAlias node, let's continue to see how the package node is parsed.

After getting the name parameter value of the package, XmlConfigBuilder completely gives it to the register aliases (string) method of TypeAliasRegistry to complete the subsequent process.

// Batch resolve aliases according to package. The default value of aliases is SimpleName of entity class
String typeAliasPackage = child.getStringAttribute("name");
// Register alias mapping to alias registry
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

In the register aliases (string) method of TypeAliasRegistry, the work is directly transferred to the register aliases (string, class) method to complete:

/**
 * Register the alias mapping relationship of the specified type and its sub implementation under the specified package
 *
 * @param packageName Specify package name
 * @param superType   Specify type
 */
public void registerAliases(String packageName, Class<!--?--> superType) {
    // Get the subclass or implementation class of all supertypes under the specified package
    ResolverUtil<class<?>&gt; resolverUtil = new ResolverUtil&lt;&gt;();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

    // Returns the currently found class
    Set<class<? extends class<?>&gt;&gt; typeSet = resolverUtil.getClasses();
    for (Class<!--?--> type : typeSet) {
        // Ignore inner classes and interfaces (including package-info.java)
        // Skip also inner classes. See issue #6
        // Ignore anonymous classes, interfaces
        if (!type.isAnonymousClass() &amp;&amp; !type.isInterface() &amp;&amp; !type.isMemberClass()) {
            // Register alias
            registerAlias(type);
        }
    }
}

Register aliases (String, Class) method has two input parameters. The parameter packageName of String type indicates the package name used to scan JAVA Class, and the parameter superType of Class type is used to restrict the Class used to register alias to be a subclass or implementation Class of superType.

In the register aliases (string, class) method, resolveutil is used to scan and filter the valid class collection under the specified package.

After obtaining the collection of classes to be processed, TypeAliasRegistry will register all classes except interface, anonymous class and member class through registerAlias(Class) method.

The registerAlias(Class) method has been known when parsing typeAlias nodes, so it will not be repeated here.

Resolvertutil, which is used to complete scanning and filtering the collection of valid classes under the specified package mentioned earlier, is a tool class provided by mybatis.

Resolvertutil defines two properties, among which classloader property of classloader type is used to scan and load class loader. The default value is thread ා currentthread(). Getcontextclassloader(). At the same time, resolvertutil exposes its getter/setter method externally. Users can use the specified class loader by calling its setter method.

/**
 * Class loader for scanning classes
 */
private ClassLoader classloader;

/**
 * Get the class loader used to scan the class. The default is {@ link thread ා currentthread() ා getcontextclassloader()}
 *
 * @return Class loader for scanning classes
 */
public ClassLoader getClassLoader() {
    return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
}

/**
 * Configure class loader for scanning classes
 *
 * @param classloader Class loader for scanning classes
 */
public void setClassLoader(ClassLoader classloader) {
    this.classloader = classloader;
}

The matches attribute of set < Class <? Extends T > & gt; type is responsible for storing all the Class sets that meet the conditions. Resolvertutil exposes its getter methods to the public:

/**
 * Set of types satisfying the condition
 */
private Set<class<? extends t>&gt; matches = new HashSet&lt;&gt;();

/**
 * Get the type collection of all matching criteria
 *
 * @return Set of types for all matching criteria
 */
public Set<class<? extends t>&gt; getClasses() {
    return matches;
}

A Test interface is also defined in resolvereutil, which is used to complete the conditional Test of filter class:

/**
 * Conditional test interface definition for filtering classes
 */
public interface Test {
    /**
     * Determine whether the incoming class meets the necessary conditions
     */
    boolean matches(Class<!--?--> type);
}

The Test interface only defines a matches method to determine whether the incoming class meets the necessary conditions.

In addition, the most important way to expose resolvereutil is to find(Test,String):

/**
 * Recursively scans the classes in the specified package and its subpackages, and performs Test tests on all found classes. Only the classes that meet the Test will be preserved.
 *
 * @param test        Test objects for filtering classes
 * @param packageName Scanned base package name
 */
public ResolverUtil<t> find(Test test, String packageName) {

    // Convert package name to file path
    String path = getPackagePath(packageName);

    try {
        // Recursively get all files under the specified path
        List<string> children = VFS.getInstance().list(path);
        for (String child : children) {
            if (child.endsWith(".class")) {
                // Handle all the following class compilation files
                addIfMatching(test, child);
            }
        }
    } catch (IOException ioe) {
        log.error("Could not read package: " + packageName, ioe);
    }
    return this;
}

The function of the find method is to recursively scan the classes in the specified package and its subpackages, and perform Test tests on all found classes. Only the classes that meet the Test conditions will be preserved.

In the find method, first convert the incoming package name packageName to a file path,

/**
  * Convert package name to file path
  *
  * @param packageName Package name
  */
 protected String getPackagePath(String packageName) {
     return packageName == null ? null : packageName.replace('.', '/');
 }

After that, we use the VFS instance configured above to recursively obtain all files under the file path, and filter out the class compilation files ending with. Class, and give them to the addIfMatching method to complete the subsequent judgment processing operation.

/**
  * If the class corresponding to the specified class name meets the specified conditions, it is added to {@ link ා matches}.
  *
  * @param test Test class for condition judgment
  * @param fqn  Fully qualified name of the class
  */
 @SuppressWarnings("unchecked")
 protected void addIfMatching(Test test, String fqn) {
     try {
         // Converts the address name to the fully qualified name format of the class with the suffix (. class) removed
         String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
         // Get class loader
         ClassLoader loader = getClassLoader();
         if (log.isDebugEnabled()) {
             log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
         }

         // Load the class
         Class<!--?--> type = loader.loadClass(externalName);
         // Judge whether the conditions can be met
         if (test.matches(type)) {
             matches.add((Class<t>) type);
         }
     } catch (Throwable t) {
         log.warn("Could not examine class '" + fqn + "'" + " due to a " +
                 t.getClass().getName() + " with message: " + t.getMessage());
     }
 }

In the addIfMatching method, the file address name is first converted to the fully qualified name format of the class, and the. Class suffix at the end is removed. Then, the class compile file corresponding to the file is loaded with the currently configured ClassLoader to get the specific JAVA type definition.

Finally, call the incoming Test to implement the matches method of the class, verify that the class is valid, and then decide whether to save it to the matches collection.

There are also two default implementations for the Test interface in resolvereutil:

  • A subclass or implementation class used to verify whether a class is a specified class
/**
 * Verify whether a class is a subclass or implementation class of the specified class
 */
public static class IsA implements Test {
    /**
     * Parent class or interface
     */
    private Class<!--?--> parent;

    /**
     * structure
     */
    public IsA(Class<!--?--> parentType) {
        this.parent = parentType;
    }

    /**
     * Determine whether a class specifies a subclass or implementation class of the class
     */
    @Override
    public boolean matches(Class<!--?--> type) {
        return type != null &amp;&amp; parent.isAssignableFrom(type);
    }

    @Override
    public String toString() {
        return "is assignable to " + parent.getSimpleName();
    }
}
  • Used to check whether the specified annotation is marked on the specified class
/**
 * Test class used to check whether the specified annotation is marked on the specified class
 */
public static class AnnotatedWith implements Test {
    /**
     * Annotation class for verification
     */
    private Class<!--? extends Annotation--> annotation;

    /**
     * structure
     */
    public AnnotatedWith(Class<!--? extends Annotation--> annotation) {
        this.annotation = annotation;
    }

    /**
     * Determine whether the specified annotation is marked on the specified class
     */
    @Override
    public boolean matches(Class<!--?--> type) {
        return type != null &amp;&amp; type.isAnnotationPresent(annotation);
    }

    @Override
    public String toString() {
        return "annotated with @" + annotation.getSimpleName();
    }
}

For these two Test implementation classes, resolvereutil also provides a wrapper implementation of the find method separately:

  • Gets the subclass / implementation class of all specified classes / interfaces under the specified package collection
/**
 * Gets the subclass / implementation class of all specified classes / interfaces under the specified package collection.
 *
 * @param parent       Class definition / interface definition used to find a subclass or implementation class
 * @param packageNames One or more package names used to find classes
 */
public ResolverUtil<t> findImplementations(Class<!--?--> parent, String... packageNames) {
    if (packageNames == null) {
        return this;
    }
    // Determine whether it is a subclass or implementation class of the specified type
    Test test = new IsA(parent);
    for (String pkg : packageNames) {
        // Package by package
        find(test, pkg);
    }
    return this;
}
  • Gets all class sets with specified annotations under the specified package set
/**
 * Gets all class sets with specified annotations under the specified package set
 *
 * @param annotation   Notes to be noted
 * @param packageNames One or more package names
 */
public ResolverUtil<t> findAnnotated(Class<!--? extends Annotation--> annotation, String... packageNames) {
    if (packageNames == null) {
        return this;
    }
    // Judge whether there is specified annotation
    Test test = new AnnotatedWith(annotation);
    for (String pkg : packageNames) {
        find(test, pkg);
    }

    return this;
}

So far, the parsing of the typeAliases element has been completed.

Pay attention to me and learn more together

Tags: Programming Java Mybatis Attribute

Posted on Fri, 26 Jun 2020 23:58:33 -0400 by picos