GroovyScriptEngine在加载使用其他类的类时引发MultipleCompationerRorsException';静态内部类

GroovyScriptEngine在加载使用其他类的类时引发MultipleCompationerRorsException';静态内部类,groovy,Groovy,我在GroovyScriptEngine中遇到了一个问题—它似乎无法处理内部类。有人知道GroovyScriptEngine中是否存在一些限制或解决方法吗 我有一个包含以下两个文件的目录: //MyClass.groovy 公共类MyClass{ 肌外膜m1; MyOuter.MyInner m2; } 及 //MyOuter.groovy 公共级MyOuter{ 公共静态类MyInner{} } 我有以下的测试课程: import java.io.File; import java.net

我在GroovyScriptEngine中遇到了一个问题—它似乎无法处理内部类。有人知道GroovyScriptEngine中是否存在一些限制或解决方法吗

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

//MyClass.groovy
公共类MyClass{
肌外膜m1;
MyOuter.MyInner m2;
}

//MyOuter.groovy
公共级MyOuter{
公共静态类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");
    }

}
我本来希望有一个“干净的编译”,但内部类似乎造成了问题


我的groovy类在使用groovyc的命令行或Eclipse中可以很好地编译。

您在这里遇到了一个边缘案例。为了澄清发生了什么,让我们定义初始条件:

  • 您有一个在JVM中执行的Java(或Groovy)类
  • 有两个Groovy类在JVM之外加载
如果您将这两个Groovy类放在执行Java类的同一路径中,您所描述的问题就不存在了——在本例中,IDE负责编译这些Groovy类,并将它们放在运行Java测试类的JVM的类路径中

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

执行时:

Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
资料来源:

这里
URL-source=resourceLoader.loadGroovySource(名称)它将完整的文件URL加载到源文件,这里
cls=recompile(源、名称、旧类)它执行类编译

Groovy类编译涉及几个方面。其中之一是
Phase.SEMANTIC\u ANALYSIS
,它分析类字段及其类型。此时,
ClassCodeVisitorSupport
用于
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);
    }
}
资料来源:

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

资料来源:

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

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;
}
资料来源:

当Groovy类加载器尝试解析它到达的
MyOuter
类型名时

lr=classNodeResolver.resolveName(名称,编译单元);
它定位名为
MyOuter.groovy
的脚本,并创建与此脚本文件名关联的
SourceUnit
对象。这很简单,就像说“好的,这个类目前不在我的类路径中,但是我可以看到一个源文件,一旦编译,它将提供一个有效类型的名称
MyOuter
”。这就是为什么它最终达到:

currentClass.getCompileUnit().addClassNodeToCompile(类型,su);
其中
currentClass
是一个与
MyClass
类型关联的对象-它将此源单元添加到
MyClass
编译单元,因此它使用
MyClass
类进行编译。这就是解决问题的关键

MyOuter m1
类属性结束

在下一步中,它选择
MyOuter.MyInner m2
属性并尝试解析其类型。请记住-
MyOuter
已正确解析,但它没有加载到类路径,因此它的静态内部类在任何作用域中都不存在。它使用与
MyOuter
相同的解析策略,但其中任何一种都适用于
MyOuter.MyInner
类。这就是为什么最终抛出这个编译异常

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

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");
    }
}
导入java.io.File;
导入java.net.MalformedURLException;
导入java.net.URL;
导入groovy.util.groovyScript引擎;
公共类TestGroovyScript引擎{
publicstaticvoidmain(字符串[]args)引发畸形的DurLexException,ClassNotFoundException{
最终文件myGroovySourceDir=新文件(“C:/myGroovySourceDir”);
最终URL[]URL={myGroovySourceDir.toURL()};
GroovyScript引擎GroovyScript引擎=新的GroovyScript引擎(URL,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass(“MyOuter”);
clazz类=groovyScriptEngine.getGroovyClassLoader().loadClass(“MyClass”);
}
}
它为什么有效?嗯,
MyOuter
类的语义分析不会引起任何问题,因为在这个阶段所有类型都是已知的。这就是加载
MyOuter
类成功的原因,它导致Groovy脚本引擎实例知道
MyOuter
M是什么
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 m1
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");
    }
}