解析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é)點,同時typeAlias和package均不允許再包含其他子節(jié)點。
其中:
-
typeAlias節(jié)點用于注冊單個別名映射關系,他有兩個可填參數(shù),type參數(shù)指向一個java類型的全限定名稱,為必填項,alias參數(shù)表示該java對象的別名,非必填,默認是使用java類的Class#getSimpleName()方法獲取的. -
package通常用于批量注冊別名映射關系,他只有一個必填的參數(shù)name,該參數(shù)指向一個java包名,包下的所有符合規(guī)則(默認是Object.class的子類)的類均會被注冊。
XmlConfigBuilder的typeAliasesElement方法對這兩種節(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é)點的alias和type參數(shù)的值,并通過反射將type轉換為實際的java類型,然后將別名注冊的操作轉交給typeAliasRegistry對象來完成,
如果用戶指定了alias參數(shù)的值,那就調(diào)用TypeAliasRegistry的resolveAlias(String,Class)方法來完成別名注冊,該方法我們前面已經(jīng)了解過了。
如果用戶沒有指定alias參數(shù)的值,注冊別名的工作就交給TypeAliasRegistry的resolveAlias(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é)點是如何解析的。
XmlConfigBuilder對package的解析工作,在得到package的name參數(shù)值之后,就完全交給了TypeAliasRegistry的registerAliases(String)方法來完成后續(xù)的流程。
// 根據(jù) package 來批量解析別名,別名默認取值為實體類的SimpleName
String typeAliasPackage = child.getStringAttribute("name");
// 注冊別名映射關系到別名注冊表
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
在TypeAliasRegistry的registerAliases(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)完成了。