[MyBatis源码分析系列] ResolverUtil
程序员文章站
2022-04-22 07:59:00
...
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());
}
}
推荐阅读
-
Java 集合系列(四)—— ListIterator 源码分析
-
spring源码分析系列5:ApplicationContext的初始化与Bean生命周期
-
Java并发系列之Semaphore源码分析
-
Java并发系列之CyclicBarrier源码分析
-
Java并发系列之ConcurrentHashMap源码分析
-
Java并发系列之CountDownLatch源码分析
-
quillJS 富文本编辑器源码分析系列1
-
Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析
-
持久层Mybatis3底层源码分析,原理解析
-
LinkedList源码分析:JDK源码分析系列