我在使用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中编译得很好。
发布于 2018-09-13 10:10:11
你在这里面临的是一种边缘情况。为了澄清发生了什么,让我们定义初始条件:
如果将这两个Groovy类放置在执行Java类的同一条路径中,则不存在所描述的问题--在本例中,IDE将注意编译这些Groovy类,并将它们放到开始运行Java测试类的JVM的类路径中。
但这不是您的情况,您正在尝试使用GroovyClassLoader (扩展URLClassLoader )将这两个Groovy类加载到运行中的JVM之外。我将尝试用最简单的话解释发生了什么,添加类型为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);
}
}
}在这里,URL source = resourceLoader.loadGroovySource(name);将完整的文件URL加载到源文件,在这里,cls = recompile(source, name, oldClass);执行类编译。
Groovy类编译涉及到几个相态。例如,分析类字段及其类型的Phase.SEMANTIC_ANALYSIS就是其中之一。此时,ClassCodeVisitorSupport visitClass(ClassNode node) for 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对象。它只是简单地说:"OK,这个类目前不在我的类路径中,但是有一个源文件我可以看到,编译后它将提供一个有效的名称MyOuter__“。这就是为什么它最终达到:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);其中currentClass是一个与MyClass类型相关联的对象-它将这个源单元添加到MyClass编译单元中,从而使用MyClass类进行编译。这就是解决问题的关键
MyOuter m1类属性结束。
在下一步中,它选择MyOuter.MyInner m2属性并尝试解析其类型。记住-- MyOuter被正确地解析了,但是它没有加载到类路径中,所以它的静态内部类还不存在于任何作用域中。它通过与MyOuter相同的解析策略,但它们中的任何一个都适用于MyOuter.MyInner类。这就是为什么ResolveVisitor.resolveOrFail()最终抛出这个编译异常的原因。
解决办法
好吧,我们知道会发生什么,但我们能做些什么吗?幸运的是,有一个解决这个问题的方法。只有先将MyClass类加载到Groovy脚本引擎中,才能运行程序并成功加载MyOuter:
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类型。因此,当您下一次从同一个Groovy脚本引擎加载MyClass时,它将应用不同的解析策略--它将发现当前编译单元可以使用这两个类,并且它将不必根据其Groovy脚本文件解析MyOuter类。
调试
如果您想更好地检查这个用例,那么运行调试器并查看在运行时发生了什么是值得的。例如,您可以在ResolveVisitor.java文件的第357行创建一个断点,以查看所描述的场景。不过,请记住一件事-- resolveFromDefaultImports(type, testDefaultImports)将尝试通过应用默认包(如java.util、java.io、groovy.lang等)查找MyClass和MyOuter类。这种解决策略在resolveToOuter(type)之前生效,因此您必须耐心地跳过它们。但这是值得的,看看,并获得一个更好的了解,如何工作。希望能帮上忙!
https://stackoverflow.com/questions/52294765
复制相似问题