首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >GroovyScriptEngine在加载使用其他类的静态内部类的类时抛出MultipleCompilationErrorsException

GroovyScriptEngine在加载使用其他类的静态内部类的类时抛出MultipleCompilationErrorsException
EN

Stack Overflow用户
提问于 2018-09-12 12:06:00
回答 1查看 935关注 0票数 3

我在使用GroovyScriptEngine时遇到了一个问题--它似乎无法处理内部类。有人知道GroovyScriptEngine或解决方案是否有一些限制吗?

我有一个包含这两个文件的目录:

代码语言:javascript
复制
// MyClass.groovy

public class MyClass {
    MyOuter m1;
    MyOuter.MyInner m2;
}

代码语言:javascript
复制
// MyOuter.groovy

public class MyOuter {
    public static class MyInner {}
}

我有以下的考试课程:

代码语言:javascript
复制
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");
    }

}

当我运行它时,我会得到以下编译错误:

代码语言:javascript
复制
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中编译得很好。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-09-13 10:10:11

你在这里面临的是一种边缘情况。为了澄清发生了什么,让我们定义初始条件:

  • 您有一个Java (或Groovy)类,它在JVM中执行
  • 有两个Groovy类被加载到JVM之外。

如果将这两个Groovy类放置在执行Java类的同一条路径中,则不存在所描述的问题--在本例中,IDE将注意编译这些Groovy类,并将它们放到开始运行Java测试类的JVM的类路径中。

但这不是您的情况,您正在尝试使用GroovyClassLoader (扩展URLClassLoader )将这两个Groovy类加载到运行中的JVM之外。我将尝试用最简单的话解释发生了什么,添加类型为MyOuter的字段不会引发任何编译错误,但是MyOuter.MyInner会抛出任何错误。

执行时:

代码语言:javascript
复制
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");

Groovy类加载器转到脚本文件查找部分,因为它无法在当前类路径中找到MyClass。这是对此负责的部分:

代码语言:javascript
复制
    // 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) for MyClass类和下面的行

代码语言:javascript
复制
node.visitContents(this);

启动类内容处理。如果我们看一下这个方法的源代码:

代码语言:javascript
复制
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

我们将看到它分析和处理类属性、字段、构造函数和方法。在此阶段,它解析为这些元素定义的所有类型。它看到有两个属性m1m2,它们相应地具有MyOuterMyOuter.MyInner类型,并为它们执行visitor.visitProperty(pn);。此方法执行我们正在寻找的方法- resolve()

代码语言:javascript
复制
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

MyOuterMyOuter.MyInner类都执行此方法。值得一提的是,类解析机制只检查给定类在类路径中是否可用,并且不加载或解析任何类。这就是为什么MyOuter在此方法到达resolveToOuter(type)时被识别的原因。如果我们快速查看它的源代码,我们将了解它为什么适用于这个类:

代码语言:javascript
复制
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类型名称时,它会到达

代码语言:javascript
复制
lr = classNodeResolver.resolveName(name, compilationUnit);

它定位名为MyOuter.groovy的脚本,并创建与此脚本文件名关联的SourceUnit对象。它只是简单地说:"OK,这个类目前不在我的类路径中,但是有一个源文件我可以看到,编译后它将提供一个有效的名称MyOuter__“。这就是为什么它最终达到:

代码语言:javascript
复制
currentClass.getCompileUnit().addClassNodeToCompile(type, su);

其中currentClass是一个与MyClass类型相关联的对象-它将这个源单元添加到MyClass编译单元中,从而使用MyClass类进行编译。这就是解决问题的关键

代码语言:javascript
复制
MyOuter m1

类属性结束。

在下一步中,它选择MyOuter.MyInner m2属性并尝试解析其类型。记住-- MyOuter被正确地解析了,但是它没有加载到类路径中,所以它的静态内部类还不存在于任何作用域中。它通过与MyOuter相同的解析策略,但它们中的任何一个都适用于MyOuter.MyInner类。这就是为什么ResolveVisitor.resolveOrFail()最终抛出这个编译异常的原因。

解决办法

好吧,我们知道会发生什么,但我们能做些什么吗?幸运的是,有一个解决这个问题的方法。只有先将MyClass类加载到Groovy脚本引擎中,才能运行程序并成功加载MyOuter

代码语言:javascript
复制
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脚本引擎实例知道什么是MyOuterMyOuter.MyInner类型。因此,当您下一次从同一个Groovy脚本引擎加载MyClass时,它将应用不同的解析策略--它将发现当前编译单元可以使用这两个类,并且它将不必根据其Groovy脚本文件解析MyOuter类。

调试

如果您想更好地检查这个用例,那么运行调试器并查看在运行时发生了什么是值得的。例如,您可以在ResolveVisitor.java文件的第357行创建一个断点,以查看所描述的场景。不过,请记住一件事-- resolveFromDefaultImports(type, testDefaultImports)将尝试通过应用默认包(如java.utiljava.iogroovy.lang等)查找MyClassMyOuter类。这种解决策略在resolveToOuter(type)之前生效,因此您必须耐心地跳过它们。但这是值得的,看看,并获得一个更好的了解,如何工作。希望能帮上忙!

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52294765

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档