org.reflections 接口通过反射获取实现类源码研究

版本 org.reflections reflections 0.9.12


Reflections通过扫描classpath,索引元数据,并且允许在运行时查询这些元数据。

使用Reflections可以很轻松的获取以下元数据信息:

获取某个类型的所有子类;比如,有一个父类是Interface,可以获取到Interface的所有子类。

  1. 获取某个注解的所有类型/字段变量,支持注解参数匹配。
  2. 使用正则表达式获取所有匹配的资源文件
  3. 获取特定签名方法。
  4. 接口通过反射获取实现类步骤和源码解析

## 第一步: 通过包名称获取 Reflections

Reflections reflections = new Reflections(pageName);

new Reflections(pageName) 详细源码解析:

ConfigurationBuilder.build(params)

1.1.将入参扁平化处理,加入 parameters 列表中

if (params != null) {
for (Object param : params) {
if (param != null) {
if (param.getClass().isArray()) { for (Object p : (Object[]) param) if (p != null) parameters.add(p); }
else if (param instanceof Iterable) { for (Object p : (Iterable) param) if (p != null) parameters.add(p); }
else parameters.add(param);
}
}
}

1.2.判断入参中是否包含类加载器,如果有类加载器就将其加入加载器列表loaders中,如果没有则创建一个空的类加载器数组classLoaders

List<ClassLoader> loaders = new ArrayList<>();
for (Object param : parameters) if (param instanceof ClassLoader) loaders.add((ClassLoader) param);

1.3.遍历扁平化处理后的入参列表 parameters:

  • 如果元素为string,Url类型则将该url加入过滤器构造器FilterBuilder中
  • 如果是Class信息则将其转换为Url再加入过滤器构造器中
  • 如果是Scanner则添加到搜索器列表scanners中
        for (Object param : parameters) {
if (param instanceof String) {
builder.addUrls(ClasspathHelper.forPackage((String) param, classLoaders));
filter.includePackage((String) param);
}
else if (param instanceof Class) {
if (Scanner.class.isAssignableFrom((Class) param)) {
try { builder.addScanners(((Scanner) ((Class) param).newInstance())); } catch (Exception e) { /*fallback*/ }
}
builder.addUrls(ClasspathHelper.forClass((Class) param, classLoaders));
filter.includePackage(((Class) param));
}
else if (param instanceof Scanner) { scanners.add((Scanner) param); }
else if (param instanceof URL) { builder.addUrls((URL) param); }
else if (param instanceof ClassLoader) { /* already taken care */ }
else if (param instanceof Predicate) { filter.add((Predicate<String>) param); }
else if (param instanceof ExecutorService) { builder.setExecutorService((ExecutorService) param); }
else if (Reflections.log != null) { throw new ReflectionsException("could not use param " + param); }
}

1.4.当 FilterBuilder 中没有任何一个url时,从类加载器中获取URL

1.4.1. 判断是否存在有效类加载器,

  • 如果不存在有效类加载器,则获取contextClassLoader当前线程的加载器和静态类加载器staticClassLoader(从org.reflections.Reflections依赖中获取加载器)作为默认加载器
            ClassLoader contextClassLoader = contextClassLoader(), staticClassLoader = staticClassLoader();
return contextClassLoader != null ?
staticClassLoader != null && contextClassLoader != staticClassLoader ?
new ClassLoader[]{contextClassLoader, staticClassLoader} :
new ClassLoader[]{contextClassLoader} :
new ClassLoader[] {};
public static ClassLoader contextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public static ClassLoader staticClassLoader() {
return Reflections.class.getClassLoader();
}

1.4.2. 判断类加载器类型,如果是 URLClassLoader 则从中获取URL,如果不是则寻找父类加载器(双子委派模型)是否是URLClassLoader,如果是则从中获取URL

        for (ClassLoader classLoader : loaders) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
if (urls != null) {
result.addAll(Arrays.asList(urls));
}
}
classLoader = classLoader.getParent();
}
}

1.5.将过滤器,类加载器,scanners等添加到 ConfigurationBuilder 环境创造器中

builder.filterInputsBy(filter);
if (!scanners.isEmpty()) {
builder.setScanners(scanners.toArray(new Scanner[scanners.size()]));
}
if (!loaders.isEmpty()) {
builder.addClassLoaders(loaders);
}
public ConfigurationBuilder filterInputsBy(Predicate<String> inputsFilter) {
this.inputsFilter = inputsFilter;
return this;
}

1.6.然后将ConfigurationBuilder(实现了Configuration) 传入Reflections的构造方法中

public Reflections(final Object... params) {
this(ConfigurationBuilder.build(params));
}
public Reflections(final Configuration configuration) {
...
}

1.7.将Store store 清空

store = new Store();

1.8.如果 configuration 中scanners不为空,遍历scanners将configuration 放到每一个scanner中

for (Scanner scanner : configuration.getScanners()) {
scanner.setConfiguration(configuration);
}

1.9.执行scan()方法进行扫描

  • 如果 configuration 中 URL 为空则直接退出并打印告警
  • 获取configuration 中线程池,如果存在线程池则用线程池异步运行protected void scan(URL url)方法,如果不存在线程池则同步运行
//线程池可以在new Reflections(pageName)时,通过入参传递
//或者采用如下方式
public ConfigurationBuilder setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}

1.10.protected void scan(URL url)

  • 先对URL进行初步格式校验和替换 file:/D:/IdeaProjects/study-netty/target/classes/ -> D:/IdeaProjects/study-netty/target/classes
  • 将文件路径进行转换
String path = file.getRelativePath();
String fqn = path.replace('/', '.');
  • 根据当前路径递归获取包下所有文件(栈的格式)
for (final Vfs.File file : dir.getFiles()){...}
  • 根据configuration中过滤器inputsFilter,使用过滤器对已获取到的文件路径进行校验
Predicate<String> inputsFilter = configuration.getInputsFilter();
f (inputsFilter == null || inputsFilter.test(path) || inputsFilter.test(fqn)){...}
//校验方式为正则校验
public boolean test(final String regex) {return pattern.matcher(regex).matches();}
  • 找到符合条件的文件路径后判断文件类型是否正确
if (scanner.acceptsInput(path) || scanner.acceptsInput(fqn)) {
classObject = scanner.scan(file, classObject, store);
}
    • 当前Scanner 类型分别为:TypeAnnotationsScanner,SubTypesScanner 的父类都是AbstractScanner,都未重写acceptsInput方法,其所需文件类型都是.class文件
public boolean acceptsInput(String file) {
return file.endsWith(".class");
}
    • ResourcesScanner 类重写了acceptsInput
public boolean acceptsInput(String file) {
return !file.endsWith(".class"); //not a class
}
  • 从文件流中获取class文件 ClassFile

  • 然后进行校验Scanner 的校验

    • TypeAnnotationsScanner 扫描运行期的注解 ,添加到store 中
    • SubTypesScanner 扫描类的父类和接口,如果允许子类反向查找,最后添加到store 中
  •   String className = getMetadataAdapter().getClassName(cls);
    String superclass = getMetadataAdapter().getSuperclassName(cls); if (acceptResult(superclass)) {
    //添加到store中
    put(store, superclass, className);
    } //获取接口,将接口和父类都放入store中
    for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(cls)) {
    if (acceptResult(anInterface)) {
    put(store, anInterface, className);
    }
    }
    //put方法如下
    protected void put(Store store, String key, String value) {
    store.put(Utils.index(getClass()), key, value);
    }
  • 判断是否需要展开父类,默认为true,

  • 从store中获取key为SubTypesScanner的map中的数据,获取接口的类和实现类信息,向上寻找其未扫描的父类,最后添加到store的key为SubTypesScanner的map中

    • 例如: A extends B, B extends C 只有A 在入参的路径中,上述的方法只能找到B接口,但是找不到最顶层的C接口,此时调用下放方法,找到最顶层接口C
    • String index = index(SubTypesScanner.class);
      Set<String> keys = store.keys(index);
      keys.removeAll(store.values(index));
      for (String key : keys) {
      final Class<?> type = forName(key, loaders());
      if (type != null) {
      expandSupertypes(store, key, type);
      }
      } private void expandSupertypes(Store store, String key, Class<?> type) {
      for (Class<?> supertype : ReflectionUtils.getSuperTypes(type)) {
      if (store.put(SubTypesScanner.class, supertype.getName(), key)) {
      if (log != null) log.debug("expanded subtype {} -> {}", supertype.getName(), key);
      expandSupertypes(store, supertype.getName(), supertype);
      }
      }
      ReflectionUtils.getSuperTypes(type) //方法查询到了父类
  • 创建出一个Reflections含有store,filter,sacnners的Reflections

## 获取子类

/**
* targetInterface 需要查询子类的接口,
*/
Set<Class<?>> implClazz = reflections.getSubTypesOf((Class<Object>) targetInterface);

public Set<Class<? extends T>> getSubTypesOf(final Class type)源码解析

2.1.从store中获取目标接口的子类

store.getAll(SubTypesScanner.class, type.getName())

2.2.通过类加载器加载当前子类,当前类加载器为null,通过上述1.4.1的方法获取默认加载器

    public static <T> Set<Class<? extends T>> forNames(final Collection<String> classes, ClassLoader... classLoaders) {
return classes.stream()
.map(className -> (Class<? extends T>) forName(className, classLoaders))
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
}

2.3 通过类全路径加载类

    public static Class<?> forName(String typeName, ClassLoader... classLoaders) {
try { return classLoader.loadClass(type); }
}

2.4 最后获取到了实现类的反射反射对象列表

org.reflections 接口通过反射获取实现类源码研究的更多相关文章

  1. java中的反射机制,以及如何通过反射获取一个类的构造方法 ,成员变量,方法,详细。。

    首先先说一下类的加载,流程.只有明确了类这个对象的存在才可以更好的理解反射的原因,以及反射的机制. 一.  类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三 ...

  2. Java实现通过反射获取指定类的所有信息

    package com.ljy; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.l ...

  3. List 接口以及实现类和相关类源码分析

    List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除( ...

  4. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  5. 获取hadoop的源码和通过eclipse关联hadoop的源码

    一.获取hadoop的源码 首先通过官网下载hadoop-2.5.2-src.tar.gz的软件包,下载好之后解压发现出现了一些错误,无法解压缩, 因此有部分源码我们无法解压 ,因此在这里我讲述一下如 ...

  6. Thread类源码剖析

    目录 1.引子 2.JVM线程状态 3.Thread常用方法 4.拓展点 一.引子 说来也有些汗颜,搞了几年java,忽然发现竟然没拜读过java.lang.Thread类源码,这次特地拿出来晒一晒. ...

  7. Java并发编程笔记之Unsafe类和LockSupport类源码分析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar ...

  8. Cocos2d-X3.0 刨根问底(六)----- 调度器Scheduler类源码分析

    上一章,我们分析Node类的源码,在Node类里面耦合了一个 Scheduler 类的对象,这章我们就来剖析Cocos2d-x的调度器 Scheduler 类的源码,从源码中去了解它的实现与应用方法. ...

  9. 从零教你如何获取hadoop2.4源码并使用eclipse关联hadoop2.4源码

    从零教你如何获取hadoop2.4源码并使用eclipse关联hadoop2.4源码http://www.aboutyun.com/thread-8211-1-1.html(出处: about云开发) ...

随机推荐

  1. 【编程思想】【设计模式】【结构模式Structural】MVC

    Python版 https://github.com/faif/python-patterns/blob/master/structural/mvc.py #!/usr/bin/env python ...

  2. 前端两大框架 vue 和 react 的区别

    1. 设计思想 vue: vue的官网介绍说vue是一种渐进式框架,采用自底向上增量开发的设计: react: 采用函数式编程,推崇纯组件,数据不可变,单向数据流: 2. 编写语法 vue: 采用单文 ...

  3. 爬虫之正则表达式re模块

    为什么要学正则表达式 实际上爬虫一共就四个主要步骤: 明确目标 (要知道你准备在哪个范围或者网站去搜索) 爬 (将所有的网站的内容全部爬下来) 取 (去掉对我们没用处的数据) 处理数据(按照我们想要的 ...

  4. 为什么volatile能保证有序性不能保证原子性

    对于内存模型的三大特性:有序性.原子性.可见性. 大家都知道volatile能保证可见性和有序性但是不能保证原子性,但是为什么呢? 一.原子性.有序性.可见性 1.原子性: (1)原子的意思代表着-- ...

  5. 因Console.Read()导致Centos 后台运行.net core程序报错

    .net 控制台程序通常用 Console.Read(),或者Console.ReadKey()让进程阻塞保持,不退出. 但在.net core 需要将程序放在后台执行时 用Console.Read( ...

  6. Redis图形管理 redis-browser

    目录 一.介绍 二.部署 三.启动 监听单台 听多台 四.报错合集 一.介绍 redis-browser是redis的web端图形化管理工具.利用它可以查看和管理redis的数据,界面简洁,能和ral ...

  7. 度量驱动的DevOps实现

    目录 一.简介 二.度量是什么 三.实践 四.QA问答 一.简介 Wiki上讲:DevOps(Development和Operations的组合词)是一种重视"软件开发人员(Dev)&quo ...

  8. Winamp栈溢出漏洞研究

    Winamp作为一款在90年代最主流的音乐播放器,到现在仍有大量的忠实粉丝.当然也存在很多漏洞.下面具体分析的就是打开软件的更新历史信息而触发的一个缓冲区溢出漏洞. 0X01漏洞重现 正常的whats ...

  9. 面试官:HashSet如何保证元素不重复?

    本文已收录<Java常见面试题>系列,Git 开源地址:https://gitee.com/mydb/interview HashSet 实现了 Set 接口,由哈希表(实际是 HashM ...

  10. 复杂SQL案例:用户退款信息查询

    供参考: select t3.course_id 课程id, t3.user_id 用户ID, u.user_full_name 姓名, -- u.phone, concat(u.company,' ...