TL;DR:我在类路径和java/groovy执行环境方面遇到了麻烦。Java是openjdk版本“17.0.7”2023-04-18。Groovy是groovy-4.0.12.jar。
背景
我有一个Java程序,可以加载并运行groovy脚本。该脚本可以通过初始化时设置的ExecutionContext
类访问父级中的许多资源。该设计使用基类来标准化接口,将特定于任务的代码留给子类。举个简单的例子:
BaseClass.1.groovy:
import org.bson.*;
class BaseClass {
ExecutionContext exe;
public void setContext(ExecutionContext executingEnvironment) {
this.exe = executingEnvironment
}
Document fetchSection(String name) {
return exe.fetchSection(name);
}
}
MyClass.groovy:
import BaseClass;
import org.bson.*;
class MyClass extends BaseClass {
void do_something(Document inputs) {
Document s1 = fetchSection("S1"); // use BaseClass method
...
}
}
ExecutionContext
在一个简单的类中定义,并放入一个jar ExecutionContext.jar
中:
import org.bson.*;
public interface ExecutionContext {
Document fetchSection(String sectionName);
}
父程序包含ExecutionContext
的具体实现:
class ExecutionContextImpl implements ExecutionContext {
public Document fetchSection(String sectionName) {
// Actual work here...
}
}
父节点首先加载BaseClass
,然后是MyClass
,然后创建MyClass
的示例,然后创建ExecutionContext
的示例,并使用反射将其设置为MyClass
:
GroovyClassLoader cls = new GroovyClassLoader();
String fwscript = get BaseClass.1.groovy from somewhere;
String myscript = get MyClass.groovy from somewhere;
Class fwclazz = cls.parseClass(fwscript);
Class clazz = cls.parseClass(myscript);
Object oo = clazz.newInstance();
ExecutionContext qqq = new ExecutionContextImpl();
Class[] argTypes = new Class[1];
argTypes[0] = ExecutionContext.class;
java.lang.reflect.Method m = fwclazz.getDeclaredMethod("setContext", argTypes);
Object[] margs = new Object[1];
margs[0] = qqq;
Object oo2 = m.invoke(oo, margs);
这是可行的,稍后使用动态反射调用方法(如do_something
)也是可行的:
Class[] argTypes = new Class[1];
argTypes[0] = Document.class;
java.lang.reflect.Method m = clazz.getDeclaredMethod( "do_something", argTypes);
Object[] margs = new Object[1];
margs[0] = new Document("adj", 6.3);
Object oo2 = m.invoke(oo, margs);
问题
我希望将groovy类路径环境与父环境隔离开来。这可以如下进行:注意arg #2中的null
,以防止新的类加载器从父程序继承环境:
URL[] groovyClasspath = new URL[] {
new File("/path/to/ExecutionContext.jar").toURI().toURL(),
new File("/path/to/groovy-4.0.12.jar").toURI().toURL(),
new File("/path/to/guava-17.0.jar").toURI().toURL(),
new File("/path/to/bson-4.9.0.jar").toURI().toURL()
};
URLClassLoader cpl = new URLClassLoader(groovyClasspath, null);
这几乎是工作。脚本解析,特别是解析脚本中引用的资源,如imports
、bson
和Table
对象。注解掉类路径中的URL会产生一个预期的unable to resolve class
运行时异常,因此它肯定会找到预期的代码。问题是,使用显式的类路径,groovy环境似乎无法在加载的groovy脚本中找到方法。此呼叫:
java.lang.reflect.Method m = fwclazz.getDeclaredMethod("setContext", argTypes);
抛出异常java.lang.NoSuchMethodException: BaseClass.setContext(ExecutionContext)
。任何对getDeclaredMethod
的调用,无论是基类fwclazz
还是子类MyClass
,都有这个问题。奇怪的是,如果我用这样的方式列出方法:
for (Method method : fwclazz.getDeclaredMethods()) {
System.out.println(" " + method.toString());
}
然后我清楚地看到public void BaseClass.setContext(ExecutionContext)
:
public groovy.lang.MetaClass BaseClass.getMetaClass()
public void BaseClass.setMetaClass(groovy.lang.MetaClass)
protected groovy.lang.MetaClass BaseClass.$getStaticMetaClass()
public org.bson.Document BaseClass.fetchSection(java.lang.String)
public void BaseClass.setContext(ExecutionContext)
public static java.lang.invoke.MethodHandles$Lookup BaseClass.$getLookup()
public ExecutionContext BaseClass.getExe()
public void BaseClass.setExe(ExecutionContext)
我确信有一个非常简单的添加到GroovyClassLoader
URL
列表中来使此工作。有线索吗?
1条答案
按热度按时间14ifxucb1#
在代码中有一个类文字
ExecutionContext.class
。此类将通过包含此表达式的代码的定义类加载器进行解析。然后,使用
new URLClassLoader(groovyClasspath, null)
创建一个新的类装入器,并将引导类装入器作为其父类。所以它不能将类名ExecutionContext
解析为上面描述的类,因为它甚至不知道类装入器。但显然,您已经将该类的一个版本添加到了新类加载器的路径中(我假设是“ExecutionContext.jar”)。因此,您有两个名为
ExecutionContext
的不同类,它们由不同的类装入器装入。在查找方法时这些不匹配。您可以为新的类加载器设置父类,如
new URLClassLoader(groovyClasspath, ExecutionContext.class.getClassLoader())
,以确保新的类加载器将看到与您的代码相同的ExecutionContext
类。类装入器在试图装入类本身之前先尝试父装入器。最好的策略是不要为父加载器已经可用的类添加源代码到新的类加载器中,以强调哪个加载器应该加载哪个类。