Mybatis源碼之美:2.6.解析typeAliases元素,完成類型別名的注冊工作

解析typeAliases元素,完成類型別名的注冊工作

點擊查看typeAliases元素的用法

typeAliases元素在mybatis中用于完成類型別名映射的配置工作,關于mybatis的類型別名機制,我們在前面已經(jīng)稍作了解,他的作用就是為指定的JAVA類型提供一個較短的名字,從而簡化我們使用完全限定名帶來的冗余,是簡化我們使用Mybatis時的代碼量的一個優(yōu)化性操作。

在Mybatis中配置自定義別名,需要使用的元素是typeAliases,typealiases的DTD定義如下::

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

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

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

根據(jù)typeAliases的DTD定義,在typealiases下允許有零個或多個typeAlias/package節(jié)點,同時typeAliaspackage均不允許再包含其他子節(jié)點。

其中:

  • typeAlias節(jié)點用于注冊單個別名映射關系,他有兩個可填參數(shù),type參數(shù)指向一個java類型的全限定名稱,為必填項,alias參數(shù)表示該java對象的別名,非必填,默認是使用java類的Class#getSimpleName()方法獲取的.
  • package通常用于批量注冊別名映射關系,他只有一個必填的參數(shù)name,該參數(shù)指向一個java包名,包下的所有符合規(guī)則(默認是Object.class的子類)的類均會被注冊。

XmlConfigBuildertypeAliasesElement方法對這兩種節(jié)點的解析工作也比較簡單:

/**
 * 解析配置typeAliases節(jié)點
 *
 * @param parent typeAliases節(jié)點
 */
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 根據(jù) package 來批量解析別名,別名默認取值為實體類的SimpleName
                String typeAliasPackage = child.getStringAttribute("name");
                // 注冊別名映射關系到別名注冊表
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 處理typeAlias配置,獲取別名和類型后執(zhí)行注冊操作

                // 別名
                String alias = child.getStringAttribute("alias");
                // java類型
                String type = child.getStringAttribute("type");

                try {
                    // 通過反射獲取java類型
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        // 未指定別名
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        // 指定別名
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

我們先看typeAlias節(jié)點的解析過程,再看package節(jié)點。

XmlConfigBuilder會依次獲取typeAlias節(jié)點的aliastype參數(shù)的值,并通過反射將type轉換為實際的java類型,然后將別名注冊的操作轉交給typeAliasRegistry對象來完成,

如果用戶指定了alias參數(shù)的值,那就調(diào)用TypeAliasRegistryresolveAlias(String,Class)方法來完成別名注冊,該方法我們前面已經(jīng)了解過了。

如果用戶沒有指定alias參數(shù)的值,注冊別名的工作就交給TypeAliasRegistryresolveAlias(Class)方法來完成:

/**
 * 注冊指定類型的別名到別名注冊表中
 * <p>
 * 在沒有注解的場景下,會將實例類型的簡短名稱首字母小寫后作為別名使用
 * <p>
 * 如果指定了{@link Alias}注解,則使用注解指定的名稱作為別名
 *
 * @param type 指定類型
 */
public void registerAlias(Class<?> type) {
    // 類別名默認是類的簡單名稱
    String alias = type.getSimpleName();
    // 處理注解中的別名配置
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        // 使用注解值
        alias = aliasAnnotation.value();
    }
    // 注冊類別名
    registerAlias(alias, type);
}

resolveAlias(Class)方法中優(yōu)先使用類型上標注的Alias注解指定的值作為別名,如果沒有標注Alias注解,那么就將該類型的簡短名稱作為別名使用。

獲取到指定類型的別名之后,具體實現(xiàn)也是交給了resolveAlias(String,Class)方法來完成.

Alias注解比較簡單,他的作用就是為指定的類型標注別名。

/**
 * 用于為指定的類提供別名
 *
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Alias {
    /**
     * 別名
     * @return 別名
     */
    String value();
}

看完了typeAlias節(jié)點的解析工作,我們繼續(xù)看package節(jié)點是如何解析的。

XmlConfigBuilderpackage的解析工作,在得到packagename參數(shù)值之后,就完全交給了TypeAliasRegistryregisterAliases(String)方法來完成后續(xù)的流程。

// 根據(jù) package 來批量解析別名,別名默認取值為實體類的SimpleName
String typeAliasPackage = child.getStringAttribute("name");
// 注冊別名映射關系到別名注冊表
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

TypeAliasRegistryregisterAliases(String)方法中,又直接將工作轉交給了registerAliases(String,Class)方法來完成:

/**
 * 注冊指定包下指定類型及其子實現(xiàn)的別名映射關系
 *
 * @param packageName 指定包名稱
 * @param superType   指定類型
 */
public void registerAliases(String packageName, Class<?> superType) {
    // 獲取指定包下所有superType的子類或者實現(xiàn)類
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

    // 返回當前已經(jīng)找到的類
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
        // Ignore inner classes and interfaces (including package-info.java)
        // Skip also inner classes. See issue #6
        // 忽略匿名類、接口
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            // 注冊別名
            registerAlias(type);
        }
    }
}

registerAliases(String,Class)方法有兩個入?yún)ⅲ渲?code>String類型的參數(shù)packageName表示用于掃描JAVA類的包名稱,Class類型的參數(shù)superType則用于限制用于注冊別名的類必須是superType的子類或者實現(xiàn)類。

registerAliases(String,Class)方法中借助于ResolverUtil來完成掃描和篩選指定包下有效類集合的工作。

在獲取到需要處理的類集合之后,TypeAliasRegistry會將除接口、匿名類以及成員類之外的所有類通過registerAlias(Class)方法完成別名注冊工作。

registerAlias(Class)方法在解析typeAlias節(jié)點時已經(jīng)有過了解,此處不再贅述。

在前文中提到的用于完成掃描和篩選指定包下有效類集合的ResolverUtil是mybatis提供的一個工具類。

ResolverUtil定義了兩個屬性,其中ClassLoader類型的classloader屬性用于掃描和加載類的類加載器,默認值是Thread#currentThread().getContextClassLoader(),同時ResolverUtil對外暴露了他的getter/setter方法,用戶可以通過調(diào)用其setter方法來使用指定的類加載器。

/**
 * 用于掃描類的類加載器
 */
private ClassLoader classloader;

/**
 * 獲取用于掃描類的類加載器,默認使用{@link Thread#currentThread()#getContextClassLoader()}
 *
 * @return 用于掃描類的類加載器
 */
public ClassLoader getClassLoader() {
    return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
}

/**
 * 配置用于掃描類的類加載器
 *
 * @param classloader 用于掃描類的類加載器
 */
public void setClassLoader(ClassLoader classloader) {
    this.classloader = classloader;
}

Set<Class<? extends T>>類型的matches屬性負責存放所有滿足條件的Class集合,ResolverUtil對外暴露了他的getter方法:

/**
 * 滿足條件的類型集合
 */
private Set<Class<? extends T>> matches = new HashSet<>();

/**
 * 獲取所有匹配條件的類型集合
 *
 * @return 所有匹配條件的類型集合
 */
public Set<Class<? extends T>> getClasses() {
    return matches;
}

ResolverUtil中還定義了一個Test接口,該接口用于完成篩選類的條件測試工作:

/**
 * 用于篩選類的條件測試接口定義
 */
public interface Test {
    /**
     * 判斷傳入的類是否滿足必要的條件
     */
    boolean matches(Class<?> type);
}

Test接口只定義了一個matches方法用于判斷傳入的類是否滿足必要的條件。

除此之外,ResolverUtil對外暴露的最主要的方法是find(Test,String):

/**
 * 遞歸掃描指定的包及其子包中的類,并對所有找到的類執(zhí)行Test測試,只有滿足測試的類才會保留。
 *
 * @param test        用于過濾類的測試對象
 * @param packageName 被掃描的基礎包名
 */
public ResolverUtil<T> find(Test test, String packageName) {

    // 將包名轉換為文件路徑
    String path = getPackagePath(packageName);

    try {
        // 遞歸獲取指定路徑下的所有文件
        List<String> children = VFS.getInstance().list(path);
        for (String child : children) {
            if (child.endsWith(".class")) {
                // 處理下面所有的類編譯文件
                addIfMatching(test, child);
            }
        }
    } catch (IOException ioe) {
        log.error("Could not read package: " + packageName, ioe);
    }
    return this;
}

find方法的作用是遞歸掃描指定的包及其子包中的類,并對所有找到的類執(zhí)行Test測試,只有滿足測試條件的類才會保留。

find方法中,首先將傳入的包名packageName轉換為文件路徑,

/**
  * 將包名轉換為文件路徑
  *
  * @param packageName 包名
  */
 protected String getPackagePath(String packageName) {
     return packageName == null ? null : packageName.replace('.', '/');
 }

之后借助前文配置的VFS實例來遞歸獲取該文件路徑下的所有文件,篩選出其中以.class為結尾的類編譯文件交給addIfMatching方法完成后續(xù)的判斷處理操作。

/**
  * 如果指定的類名對應的類滿足指定的條件,則將其添加到{@link #matches}中。
  *
  * @param test 用于條件判斷的測試類
  * @param fqn  類的全限定名稱
  */
 @SuppressWarnings("unchecked")
 protected void addIfMatching(Test test, String fqn) {
     try {
         // 將地址名稱轉換為類的全限定名稱格式,并去掉后綴(.class)
         String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
         // 獲取類加載器
         ClassLoader loader = getClassLoader();
         if (log.isDebugEnabled()) {
             log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
         }

         // 加載該類
         Class<?> type = loader.loadClass(externalName);
         // 判斷是否能滿足條件
         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());
     }
 }

addIfMatching方法中,首先將文件地址名稱轉換為類的全限定名稱格式,并移除結尾的.class后綴,之后利用當前配置的ClassLoader加載該文件對應的類編譯文件得到具體的JAVA類型定義。

最后調(diào)用傳入的Test實現(xiàn)類的matches方法,校驗獲取到的類是否有效,進而決定是否保存至matches集合中。

ResolverUtil中還為Test接口提供了兩個默認實現(xiàn):

  • 一個用于校驗某個類是否是指定類的子類或者實現(xiàn)類
/**
 * 校驗某個類是否是指定類的子類或者實現(xiàn)類
 */
public static class IsA implements Test {
    /**
     * 父類或者接口
     */
    private Class<?> parent;

    /**
     * 構造
     */
    public IsA(Class<?> parentType) {
        this.parent = parentType;
    }

    /**
     * 判斷某個類是否指定類的子類或者實現(xiàn)類
     */
    @Override
    public boolean matches(Class<?> type) {
        return type != null && parent.isAssignableFrom(type);
    }

    @Override
    public String toString() {
        return "is assignable to " + parent.getSimpleName();
    }
}
  • 一個用于檢查指定的類上是否標注了指定注解
/**
 * 用于檢查指定的類上是否標注了指定注解的測試類
 */
public static class AnnotatedWith implements Test {
    /**
     * 用于校驗的注解類
     */
    private Class<? extends Annotation> annotation;

    /**
     * 構造
     */
    public AnnotatedWith(Class<? extends Annotation> annotation) {
        this.annotation = annotation;
    }

    /**
     * 判斷指定類上是否標注了指定的注解
     */
    @Override
    public boolean matches(Class<?> type) {
        return type != null && type.isAnnotationPresent(annotation);
    }

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

而且針對這兩Test實現(xiàn)類,ResolverUtil還單獨對外提供了相關的find方法的包裝實現(xiàn):

  • 獲取指定包集合下,所有指定類/接口的子類/實現(xiàn)類
/**
 * 獲取指定包集合下,所有指定類/接口的子類/實現(xiàn)類。
 *
 * @param parent       用于查找子類或者實現(xiàn)類的類定義/接口定義
 * @param packageNames 用于查找類的一個或多個包名
 */
public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
    if (packageNames == null) {
        return this;
    }
    // 判斷是否是指定類型的子類或者實現(xiàn)類
    Test test = new IsA(parent);
    for (String pkg : packageNames) {
        // 挨個處理包
        find(test, pkg);
    }
    return this;
}
  • 獲取指定包集合下所有標注了指定注解的類集合
/**
 * 獲取指定包集合下所有標注了指定注解的類集合
 *
 * @param annotation   應被標注的注解
 * @param packageNames 一個或多個包名
 */
public ResolverUtil<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
    if (packageNames == null) {
        return this;
    }
    // 判斷是否有指定注解
    Test test = new AnnotatedWith(annotation);
    for (String pkg : packageNames) {
        find(test, pkg);
    }

    return this;
}

到這,typeAliases元素的解析工作也已經(jīng)完成了。

關注我,一起學習更多知識

關注我
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容