[MyBatis原始碼分析系列] ResolverUtil
阿新 • • 發佈:2018-11-09
ResolverUtil
ResolverUtil用於查詢在類路徑可用並滿足任意條件的類。最常見的兩種情況是一個類繼承或實現了另一個類,或者此類被指定的註解標記了。然而,通過使用Test類,可以滿足任意條件的搜尋。
類載入器用於定位類路徑下指定包下面的必要類,然後載入並檢驗他們。預設使用Thread.currentThread().getContextClassLoader()
,但是在呼叫任何find()
方法之前可以通過呼叫setClassLoader()
方法來設定類載入器。
通常情況下,提供一個包名和Test例項並呼叫find()
方法初始化一個搜尋。這導致瀏覽該包和其下的所有子包來查詢匹配的類。這也是通用的工具方法來瀏覽多個包或繼承了指定的類,或標記了指定註解的類。
ResolverUtil
類通常的標準用法如下
ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
resolver.findImplementation(ActionBean.class, pkg1, pkg2);
resolver.find(new CustomTest(), pkg1);
resolver.find(new CustomTest(), pkg2);
Collection<ActionBean> beans = resolver.getClasses ();
用法
查詢com.jiaglei
包下所有Object
的子類,也就是所有類。
public class ResolverUtilTest{
@Test
public void testFind(){
ResolverUtil<Class<?>> util = new ResolverUtil<>();
util.find(new ResolverUtil.isA(Object.class) "com.jianglei");
Set<Class<?>> set = util.getClasses( );
}
}
查詢"com.jianglei.test"
包下標記有@Test
註解的類。
public class ResolverUtilTest{
@Test
public void testFind(){
ResolverUtil<Class<?>> util = new ResolverUtil<>();
util.find(new ResolverUtil.annotatedWith(Test.class), "com.jianglei.test");
Set<Class<?>> set = util.getClasses();
}
}
原始碼
public class ResolverUtil<T>{
private static final Log log = LogFactory.getLog(ResolverUtil.class);
/**
* 存放匹配類的集合。
*/
private Set<Class<? extends T>> matches = new Hashet<>();
/**
* 用於查詢類的類載入器。
*/
private ClassLoader classLoader;
public Set<Class<? extends T>> getClasses(){
return matches;
}
public ClassLoader getClassLoaer(){
return classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
}
public void setClassLoader(ClassLoader classloader){
this.classloader = classloader;
}
}
/**
* 一個簡單的介面指定了怎麼測試的類則被包含進ResolverUtil的結果集裡
*/
public interface Test{
/**
* 將會反覆被呼叫當測試指定的類時。
*/
boolean matches(Class<?> type);
}
/**
* 測試是否是指定類的子類。
*/
public static class IsA implements Test{
private Class<?> parent;
public IsA(Class<?> parentType){
this.parent = parentType;
}
@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.isAnnotationPersent(annotation);
}
@Override
public String toString(){
return "annotated with @" + annotation.getSimpleName();
}
}
嘗試發現指定型別的子類。
public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames){
if(packageNames == null){
return this;
}
Test test = new IsA(parent);
for(String pkg : packageNames){
find(test, pkg);
}
return this;
}
試著發現註解了指定註解的類。發現的類可以通過getClasses()方法獲得。
/**
* 試著發現註解了指定註解的類。發現的類可以通過getClasses()方法獲得。
*
* @param annotation 應該出現在匹配到的類上的註解
* @param packageName 瀏覽的包名
*/
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;
}
瀏覽提供的包及其子包的類。
/**
* 瀏覽提供的包及其子包的類。
* 每個發現的類都會被提供給Test類,如果Test類的方法返回true就保留。
* 積累的類可以通過getClasses()方法獲得。
* @param test 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);
}
}
把Java類的包名轉換成路徑名,可以使用ClassLoader.getResources呼叫。
/**
* 把Java類的包名轉換成路徑名,可以使用ClassLoader.getResources呼叫。
* @param packageName 要轉換成路徑的Java包名
*/
protected String getPackagePath(String packageName){
return packageName == null ? null : packageName.replace('.', '/');
}
如果給定的類通過test的條件,則加入。
/**
* 如果給定的類通過test的條件,則加入。
* @param test test類用於測試給定的fqn類是否符合條件。
* @param fqn 類的全限定名
*/
@SuppressWarnings("unchecked")
protected void addIfMatching(Test test, String fqn){
try{
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());
}
}