首页 > 解决方案 > Java - 查找从类加载器返回特定类型的所有方法

问题描述

我正在寻找一种方法来从我的类加载器中找到所有类的所有方法,该类加载器返回某种类型。例如List.

我尝试使用这个库 - https://github.com/ronmamo/reflections

Reflections reflections = new Reflections("com",new MethodParameterScanner());

        Set<Method> methodsReturn = reflections.getMethodsReturn(List.class);
        methodsReturn
                .forEach(c -> System.out.println(c.getDeclaringClass() + "-" + c.getName()));

但因这个例外而失败。

org.reflections.ReflectionsException: could not get type for name com.intellij.rt.execution.CommandLineWrapper$AppData.access
    at org.reflections.ReflectionUtils.forName(ReflectionUtils.java:312)
    at org.reflections.util.Utils.getMemberFromDescriptor(Utils.java:67)
    at org.reflections.util.Utils.getMethodsFromDescriptors(Utils.java:88)

有没有其他方法可以做到这一点?

标签: javareflection

解决方案


你想要的是不可能的。反射是一种泄漏的抽象;没有保证的黑客攻击。

问题是这ClassLoader是一个抽象,它基本上只有以下可用的奇异操作:

  • 给定资源名称,返回类加载器可以找到的所有版本的资源;通常,几乎总是,这将是 0 或 1 个项目。“资源”是一个 URL;InputStream一个可以保证被这个加载器变成一个。

就是这样

请注意,没有方法可以请求所有资源的列表,因此,任何尝试从类加载器中“获取所有具有属性 X 的资源”的尝试都被破坏了。这就像试图从石头上取水一样。你无法得到那里没有的东西。

反射黑客所做的是检查它是哪种类加载器。如果它将类加载器识别为从具有列出所有操作的位置(例如 jar 文件或文件系统目录)获取资源的事物,它将获取底层位置并对这些资源执行直接“列表”操作,但是,如果所涉及的类加载器不是反射识别的类加载器,或者不是由具有列表操作的存储支持的类加载器,那么反射要么默默地忽略它,要么崩溃。

这就引出了一个必然的结论:你不想做你说你想做的事;java不支持它。

即使您接受除非仅使用基于基本 jar/dir 的类加载器,否则您的程序将失败,您正在做的是将 java 转换为结构类型的语言。Java 不是结构类型的;它是围绕名义打字设计的。如果您效仿,Java 不仅会更好地工作,而且您正在做其他编写 Java 代码的人认为永远不会发生的事情。

例如,如果您说:“我正在寻找各种相机,所以我将扫描整个类路径以查找具有签名方法的任何类,并void shoot(Person p)在自拍选项列表中提供它”,那么您当课程以某种方式最终出现在您的类路径中时,您将度过一段非常糟糕的时光。Gun

在 java 中,两个方法基本上是不相关的,即使它们具有相同的名称 - 方法由 ENTIRE 签名定义,包括它所在的类型。这一事实贯穿于 java 的语言设计中。

那么,你如何在java中这样的事情呢?

您可能想要的是 SPI。

  • SPI 现在和永远都与所有类加载器 100% 兼容。
  • Java 本身在很多地方都使用了 SPI。
  • SPI 很常见,以至于许多 IDE 和构建系统中都内置了插件和工具。
  • 它仅适用于名义打字。

以下是 SPI 的工作原理:

  • 定义一个接口,或者如果必须的话,定义一个(抽象)类。
  • “插件”将做两件事:
  • [1] 创建一个可实例化的类(公共的,非抽象的,有一个公共的无参数构造函数),[2] 实现/扩展我们在第一步中创建的类型,并且 [3] 列出它的完全限定名称在一个名为META-INF/services/com.foo.full.package.name.ThatInterfaceFromStep1.

然后,主要工具执行以下操作:

  • 向类加载器询问与name 匹配的所有META-INF/services/com.foo.full.package.name.ThatInterfaceFromStep1资源,然后从所有这些中读取所有行。
  • 然后它将以这种方式找到的每个名称进行类加载,并将它们作为实现提供。您可以将所有这些视为 的实例ThatInterfaceFromStep1,因为它们是。不会发生意外的铅中毒。

您可以使用注释处理器,这样您所要做的就是编写,例如:

@Provides(ImageSaver.class)
public class Jpg2000Saver implements ImageSaver {
    @Override public String description() {
        return "Joint Photographic Experts Group 2000 (JP2)";
    }

    @Override public void write(Image image, OutputStream target) throws IOException {
        // .... implement here
    }
}

并且相关的注释处理器将负责为您制作该 META-INF 文件。或者,自己制作该文件;例如,如果您使用 maven,请将其放入src/resources. 要“加载” SPI 文件,它内置于 java 本身:ServiceLoader


推荐阅读