我遇到了GroovyScriptEngine的问题-似乎无法使用内部类。有人知道GroovyScriptEngine中是否存在某些限制或解决方法?
我有一个包含这两个文件的目录:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
和
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
我有以下测试课:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
运行它时,出现以下编译错误:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
@ line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
我原本希望“干净的编译”,但是内部类似乎引起了问题。
我的groovy类可以在groovyc或Eclipse的命令行中正常编译。
您在这里遇到了一个极端情况。为了澄清会发生什么,让我们定义初始条件:
如果将这两个Groovy类放在执行Java类的相同路径中,则不会存在您描述的问题-在这种情况下,IDE会小心地编译这些Groovy类并将它们放入开始使用的JVM的类路径中运行您的Java测试类。
但这不是您的情况,您正在尝试使用GroovyClassLoader
(扩展URLClassLoader
btw)在运行的JVM之外加载这两个Groovy类。我将尝试用最简单的词来解释发生什么情况,即类型的添加字段MyOuter
不会引发任何编译错误,但MyOuter.MyInner
会引发任何编译错误。
执行时:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy类加载器转到脚本文件查找部分,因为它无法MyClass
在当前类路径中找到。这是对此负责的部分:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
来源:src / main / groovy / lang / GroovyClassLoader.java#L733-L753
在此URL source = resourceLoader.loadGroovySource(name);
它将完整的文件URL加载到源文件,并在cls = recompile(source, name, oldClass);
此执行类编译。
Groovy类的编译涉及多个阶段。其中之一是Phase.SEMANTIC_ANALYSIS
例如分析类字段及其类型。在这一点上ClassCodeVisitorSupport
执行visitClass(ClassNode node)
的MyClass
类和下面的行
node.visitContents(this);
开始上课内容处理。如果我们看一下该方法的源代码:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
来源:src / main / org / codehaus / groovy / ast / ClassNode.java#L1066-L108
我们将看到它分析并处理了类的属性,字段,构造函数和方法。在此阶段,它将解析为这些元素定义的所有类型。它看到有两个属性m1
,分别m2
具有类型MyOuter
和属性MyOuter.MyInner
,并为其执行visitor.visitProperty(pn);
。此方法执行我们正在寻找的方法-resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
来源:src / main / org / codehaus / groovy / control / ResolveVisitor.java#L343-L378
该方法同时针对MyOuter
和MyOuter.MyInner
类执行。值得一提的是,类解析机制仅检查给定的类在类路径中是否可用,并且不加载或解析任何类。这就是为什么MyOuter
此方法达到时会被识别的原因resolveToOuter(type)
。如果快速浏览一下其源代码,我们将理解为什么它适用于此类:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
来源:src / main / org / codehaus / groovy / control / ResolveVisitor.java#L725-L751
当Groovy类加载器尝试解析MyOuter
类型名称时,它会到达
lr = classNodeResolver.resolveName(name, compilationUnit);
它使用名称查找脚本,MyOuter.groovy
并创建SourceUnit
与此脚本文件名关联的对象。就像说“好吧,该类目前不在我的类路径中,但是有一个源文件,我可以看到,一旦编译,它将提供有效的名称类型MyOuter
”。这就是为什么它最终达到的原因:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
currentClass
与MyClass
类型相关联的对象在哪里-它将此源单元添加到MyClass
编译单元,因此可以使用MyClass
该类进行编译。这就是解决的关键
MyOuter m1
类属性结束。
在下一步中,它选择MyOuter.MyInner m2
属性,然后尝试解析其类型。请记住-MyOuter
正确解析,但是没有加载到类路径,因此它的静态内部类在任何范围内都不存在。它采用与相同的解决策略MyOuter
,但其中任何一种都适用于MyOuter.MyInner
课堂。这就是为什么ResolveVisitor.resolveOrFail()
最终抛出此编译异常的原因。
好的,我们知道会发生什么,但是我们能做些什么吗?幸运的是,有解决此问题的方法。MyClass
只有MyOuter
先将类加载到Groovy脚本引擎,才能运行程序并成功加载:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
为什么行得通?好的,MyOuter
类的语义分析不会引起任何问题,因为在此阶段所有类型都是已知的。这就是为什么加载MyOuter
类成功并导致Groovy脚本引擎实例知道类型MyOuter
和MyOuter.MyInner
类型的原因。因此,当您下次MyClass
从同一个Groovy脚本引擎加载时,它将应用不同的解析策略-它会找到当前编译单元可用的两个类,并且不必MyOuter
根据其Groovy脚本文件来解析类。
如果您想更好地研究此用例,则值得运行调试器并查看在运行时发生的情况。例如,您可以在ResolveVisitor.java
文件的第357行创建一个断点,以查看所描述的实际情况。请记住一两件事,虽然-resolveFromDefaultImports(type, testDefaultImports)
将尝试查找MyClass
和MyOuter
类应用默认包一样java.util
,java.io
,groovy.lang
之前等,这决心策略踢resolveToOuter(type)
,所以你必须耐心地通过他们跳。但是值得一看,并更好地了解事物的运作方式。希望能帮助到你!
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句