在加载使用其他类的静态内部类的类时,GroovyScriptEngine抛出MultipleCompilationErrorsException [英] GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class
问题描述
我遇到了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中很好地编译.
您在这里遇到了一个小问题.为了澄清会发生什么,让我们定义初始条件:
- 您有一个Java(或Groovy)类,可以在JVM内部执行
- 您有两个在JVM之外加载的Groovy类
如果将这两个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);
}
}
}
在这里URL source = resourceLoader.loadGroovySource(name);
它将完整的文件URL加载到源文件,在这里cls = recompile(source, name, oldClass);
它将执行类编译.
有几个 Groovy类编译所涉及的阶段.其中之一是 查找名称为 其中, class属性结束. 在下一步中,选择 为什么起作用?好吧, 如果您想更好地研究此用例,则值得运行调试器并查看在运行时发生的情况.例如,您可以在 I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround? I have a directory with these two files: and I have a following test class: When I run it I get the following compilation error: I would have expected a "clean compile", but the inner class seems to be causing problems. My groovy classes compile fine at the command line using groovyc, or in Eclipse. You have faced an edge case here. To clarify what happens let's define the initial conditions: The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class. But this is not your case and you are trying to load these two Groovy classes outside the running JVM using When you execute: Groovy class loader goes to script file lookup part, because it was not able to find
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753 Here There are several phases involved in Groovy class compilation. One of them is starts class contents processing. If we take a look at the source code of this method:
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108 we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378 This method gets executed for both
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751 When Groovy class loader tries to resolve which locates script with a name where class property ends. In the next step it picks OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load Why does it work? Well, semantic analysis of If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of 这篇关于在加载使用其他类的静态内部类的类时,GroovyScriptEngine抛出MultipleCompilationErrorsException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!Phase.SEMANTIC_ANALYSIS
,例如,它分析类字段及其类型.此时ClassCodeVisitorSupport
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
类.这就是为什么MyOuter
类的语义分析不会引起任何问题,因为在此阶段所有类型都是已知的.这就是为什么MyOuter
类加载成功并导致Groovy脚本引擎实例知道MyOuter
和MyOuter.MyInner
类型是什么的原因.因此,当您下次从同一个Groovy脚本引擎加载MyClass
时,它将应用不同的解析策略-它会找到当前编译单元可用的两个类,并且不必根据其Groovy脚本文件来解析MyOuter
类. /p>
调试
ResolveVisitor.java
文件的第357行创建一个断点,以查看所描述的实际情况.不过请记住一件事-resolveFromDefaultImports(type, testDefaultImports)
将尝试通过应用默认包(例如java.util
,java.io
,groovy.lang
等)来查找MyClass
和MyOuter
类.此解决策略在resolveToOuter(type)
之前启动您必须耐心地跳过它们.但是值得一看,并更好地了解事物的运作方式.希望对您有帮助!// 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)
GroovyClassLoader
(which extends URLClassLoader
btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter
does not throw any compilation error, but MyOuter.MyInner
does.Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
MyClass
in the current classpath. This is the part responsible for it: // 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);
it loads the full file URL to the source file and here cls = recompile(source, name, oldClass);
it executes class compilation. Phase.SEMANTIC_ANALYSIS
which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport
executes visitClass(ClassNode node)
for MyClass
class and following linenode.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);
}
}
m1
and m2
with types MyOuter
and MyOuter.MyInner
accordingly, and it executes visitor.visitProperty(pn);
for them. This method executes the one we are looking for - 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);
}
MyOuter
and MyOuter.MyInner
classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter
gets recognized when this method reaches resolveToOuter(type)
. If we take a quick look at its source code we will understand why it works for this class: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;
}
MyOuter
type name it reacheslr = classNodeResolver.resolveName(name, compilationUnit);
MyOuter.groovy
and it creates a SourceUnit
object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter
". This is why it finally reaches:currentClass.getCompileUnit().addClassNodeToCompile(type, su);
currentClass
is an object associated with MyClass
type - it adds this source unit to MyClass
compilation unit, so it gets compiled with the MyClass
class. And this is the point where resolvingMyOuter m1
MyOuter.MyInner m2
property and it tries to resolve its type. Keep in mind - MyOuter
got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter
, but any of them works for MyOuter.MyInner
class. And this is why ResolveVisitor.resolveOrFail()
eventually throws this compilation exception.Workaround
MyClass
successfully only if you load MyOuter
class to Groovy script engine first: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
class does not cause any problems, because all types are known at this stage. This is why loading MyOuter
class succeeds and it results in Groovy script engine instance knows what MyOuter
and MyOuter.MyInner
types are. So when you next load MyClass
from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter
class based on its Groovy script file.Debugging
ResolveVisitor.java
file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports)
will try to lookup MyClass
and MyOuter
classes by applying default packages like java.util
, java.io
, groovy.lang
etc. This resolve strategy kicks in before resolveToOuter(type)
so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!