2

我有这种问题。我在 *.clj 文件中的 Eclipse 插件(表示为 A)中包含 Clojure 代码。我不想要 AOT 编译。但是,我需要从另一个 Clojure 插件 B 加载 clojure 代码。当 B 依赖于 A 时,这是可能的。Clojure 可以轻松访问类路径并且一切正常。但是我希望将插件 A 作为 B 的扩展插入。但是有一个问题,因为我找不到如何从 B 中包含的 *.clj 文件加载 A 中包含的 Clojure 文件的方法。我想使用可以从类路径加载 *.clj 文件的 Clojure 'load' 函数,但是当我像这样明确启动插件时,这个函数只是看不到插件 A 事件的内容

 (org.eclipse.core.runtime.Platform/getBundle "A")

对洛朗回答的反应

劳伦特,非常感谢你!这很有趣。但是,我认为这可能比我原来的问题解决了一个更难的问题。您描述了如何从 java 插件中调用 clojure 代码,这非常棒。我需要从 clojure 插件调用 clojure 代码,我认为这可能更容易。我想我会创建扩展点并提供这样的 clojure 函数

<extension point="transforms">
  <function namespace="my.nemaspace" fn="my-transform"/>
</extension>

所以我不需要任何关于 IExecutableExtensionFactory 的魔法。我可以从 clojure 代码中读取扩展注册表。我不能做的是加载扩展中指定的功能。这是可行的还是我只是误解了什么?我注意到您正在使用 clojure.osgi。这看起来很酷,该项目是否有任何文档?

4

2 回答 2

6

您在类加载器方面遇到的问题是可以预料的:如果您的 Eclipse 插件/OSGi 捆绑包的依赖项是 A -> B,并且 Clojure jar 从 B 引导,则无法从 B 中看到 A 的资源是正常的。

Eclipse 插件的依赖关系之间不能有循环,因此类加载器层次结构之间不能有循环。

如果您要使用常规 Eclipse 机器从 A 向 B 编写扩展,这与您将面临的问题相同:插件 B 将声明一个接口和一个扩展点。然后插件 A 可以实现接口,并声明扩展点的扩展。最后一部分允许 Eclipse 框架对包做一些技巧:它看到 A 声明了对 B 的扩展,因此从 A 中实例化了一个类,实现了在 B 中声明的接口(这有效,因为 A 依赖于 B),并给 B来自 A 的实现实例也可以,因为它实现了 B 中的接口!

(不确定这是否清楚)。

无论如何,回到用 Clojure 编写的插件。
使用 Clojure,您没有开箱即用的类加载器提供的这种单独的环境,因为所有内容都聚合在一个“Clojure 环境”中,该环境位于嵌入 clojure jar 的插件的类加载器领域中。因此,一种可能性是,当插件 A 启动时,从 A 加载相关的命名空间。然后它们将在正确的时间加载,并可供任何其他 Clojure 代码使用。另一种可能性是使用扩展点/扩展机器。Eclipse 提供了一种使用“工厂”创建扩展点实例的方法。在逆时针方向,我们利用了这个特性,并有一个通用工厂类(用 java 编写,所以没有 AOT)负责从正确的包中加载正确的命名空间。

这里有更多关于如何扩展扩展点的细节。

逆时针的一个例子:
Eclipse 框架中有一个现有的扩展点,用于为控制台内容提供超链接检测器。逆时针扩展此扩展点以添加 nrepl 超链接。
在 Java 世界中,您必须在您的扩展中直接声明您的某个实现接口 IPatternMatchListenerDelegate 的类。
但是对于CCW,可能出于与您相同的原因,我会不惜一切代价避免AOT,所以我不能在扩展中给出java类名,或者我不得不用java编写并编译它,或者gen-class在 Clojure 中编写一个并 AOT 编译它。

相反,CCW 充分利用了 plugin.xml 的可能性:几乎在每个地方,当您必须提供类名时,您可以提供一个IExecutableExtensionFactory的实例, create()Eclipse 框架将调用它的方法来创建想要的班级。

这使我可以编写一个通用类来调用 Clojure 世界:我只是使用类名ccw.util.GenericExecutableExtension代替我应该编写的类名

从plugin.xml中提取:

<extension point="org.eclipse.ui.console.consolePatternMatchListeners">
<consolePatternMatchListener
   id="ccw.editors.clojure.nREPLHyperlink"
   regex="nrepl://[^':',' ']+:\d+">
    <class class="ccw.util.GenericExecutableExtension">
       <parameter
             name="factory"
             value="ccw.editors.clojure.nrepl-hyperlink/factory">
       </parameter>
    </class>
</consolePatternMatchListener>

注意class属性,以及如何通过parameter元素为工厂提供参数(工厂必须实现接口IExecutableExtension才能使用参数进行初始化)。

最后,您可以看到在 namespaceccw.editors.clojure.nrepl-hyperlink中,函数工厂非常简单,只需调用make函数:

(defn make []
  (let [state (atom nil)]
    (reify org.eclipse.ui.console.IPatternMatchListenerDelegate
      (connect [this console]  (dosync (reset! state console)))
      (disconnect [this]       (reset! state nil))
      (matchFound [this event] (match-found event @state)))))

(defn factory "plugin.xml hook" [ _ ] (make))

请注意,我将其作为示例进行展示,并且逆时针方向的相关代码尚未准备好作为独立库“可供使用”发布。
但是您仍然应该能够推出自己的解决方案(一旦您将所有部分都放在脑海中就很容易了)。

希望有帮助,

——洛朗

于 2012-10-02T19:54:50.760 回答
0

我想到了另一个解决方案:在 Eclipse 中,尽管我在之前的回答中说过,有可能在类加载器之间创建循环依赖关系!

Eclipse 人员需要引入这一点,以便某些类型的库(log4j 等)可以在 OSGi 环境中工作(这是 Eclipse 所基于的)。

这需要利用Eclipse-BuddyPolicy机制(第三方库和类加载)。

这很简单:如果你想让插件 B 看到插件 A 的所有类和资源,只需将其添加到插件 B 的META-INF/MANIFEST.MF文件中:

Eclipse-BuddyPolicy: dependent

上面的行表明插件 B 的类加载器将能够访问其依赖类加载器可以访问的内容。

我创建了一组名为 A 和 B 的插件示例,其中 B 有 2 个命令(在顶部菜单中可见):第一个通过调用 B 中的 clojure 代码对“hello”硬编码字符串应用文本转换。第二个动态加载来自插件 A 的新文本转换,因此当您再次调用第一个命令时,您会看到应用来自 B 的转换和来自 A 的转换的结果。

毕竟,在您的情况下,甚至可能根本不需要使用 Eclipse 扩展点/扩展机制。这完全取决于您如何打算让插件 B “发现”插件 A 的相关信息。

github 存储库显示了这一点:https ://github.com/laurentpetit/stackoverflow-12689605

高温下,

——洛朗

于 2012-10-06T23:36:36.113 回答