当我查看使用@ groovy.transform.CompileStatic编译的Groovy脚本的字节码时,我看到了一些奇怪的结果
这是复制问题的最简单的类:
@groovy.transform.CompileStatic
class ScriptTestClass{
void test_method(String x,String y,String z){
x = "foo";
}
}
当编译为字节码时,我得到这个字节码(javap -c -v ScriptTestClass.class的结果,仅针对主题方法编辑):
javap -c -v ScriptTestClass.class的结果(虽然只是主题方法):
public void test_method(java.lang.String, java.lang.String, java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=4
0: ldc #32 // String foo
2: astore 4
4: aload 4
6: astore_1
7: aload 4
9: pop
10: return
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LScriptTestClass;
0 10 1 x Ljava/lang/String;
0 10 2 y Ljava/lang/String;
0 10 3 z Ljava/lang/String;
LineNumberTable:
line 4: 0
显然, ASTORE / ALOAD 4 在这里不合适。事实上,它们看起来无关紧要。如果删除了字节码,则字节码是正确的。正确的字节码(我在Java类中编写相同的代码时得到的是:
public void test_method(java.lang.String, java.lang.String, java.lang.String);
flags: ACC_PUBLIC
Code:
stack=1, locals=4, args_size=4
0: ldc #7 // String foo
2: astore_1
3: return
LineNumberTable:
line 26: 0
line 27: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Ltesting/ScriptTestClass;
0 4 1 x Ljava/lang/String;
0 4 2 y Ljava/lang/String;
0 4 3 z Ljava/lang/String;
为什么@CompileStatic产生访问局部变量槽4的字节码?
我正在使用d Groovy版本2.3.7和Java 1.7
如果它是相关的,我用来从Groovy源获取字节的代码如下。
此代码将脚本解析为类并获取字节:
protected ClassNode loadGroovyTestClassAsBytecode(String classSource) throws Exception{
ClassNode classNode = new ClassNode();
String scriptName = "ScriptTestClass.groovy";
Class groovyClass = groovyClassLoader.parseClass(classSource,scriptName);
String className = groovyClass.getName() + ".class";
byte[] classBytes = groovyClassLoader.getClassBytes(className);
}
' groovyClassLoader'上面是以下类加载器的实例,它允许在加载后获取字节:
public class CachingGroovyClassLoader extends GroovyClassLoader {
private Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
public CachingGroovyClassLoader(){
}
public CachingGroovyClassLoader(ClassLoader parent){
super(parent);
}
public byte[] getClassBytes(String name) throws IOException{
return IOUtils.toByteArray(getResourceAsStream(name));
}
@Override
public InputStream getResourceAsStream(String name) {
if (classBytes.containsKey(name)) {
return new ByteArrayInputStream(classBytes.get(name));
}
return super.getResourceAsStream(name);
}
@Override
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
// These six lines copied from Groovy itself, with the intention to
// return a subclass
InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
public InnerLoader run() {
return new InnerLoader(CachingGroovyClassLoader.this);
}
});
return new BytecodeClassCollector(classBytes, loader, unit, su);
}
public static class BytecodeClassCollector extends ClassCollector {
private final Map<String, byte[]> classBytes;
public BytecodeClassCollector(Map<String, byte[]> classBytes, InnerLoader loader, CompilationUnit unit,
SourceUnit su) {
super(loader, unit, su);
this.classBytes = classBytes;
}
@Override
protected Class<?> onClassNode(ClassWriter classWriter, ClassNode classNode) {
classBytes.put(classNode.getName() + ".class", classWriter.toByteArray());
return super.onClassNode(classWriter, classNode);
}
}
}
编辑:
我有一个发布原始类文件的请求,但我不知道该怎么做。这里 还有一些可能有用的东西:
javap ScriptTestClass.class的结果:
Compiled from "ScriptTestClass.groovy"
public class ScriptTestClass implements groovy.lang.GroovyObject {
public static transient boolean __$stMC;
public static long __timeStamp;
public static long __timeStamp__239_neverHappen1417366615662;
public ScriptTestClass();
public void test_method(java.lang.String, java.lang.String, java.lang.String);
public java.lang.Object this$dist$invoke$1(java.lang.String, java.lang.Object);
public void this$dist$set$1(java.lang.String, java.lang.Object);
public java.lang.Object this$dist$get$1(java.lang.String);
protected groovy.lang.MetaClass $getStaticMetaClass();
public groovy.lang.MetaClass getMetaClass();
public void setMetaClass(groovy.lang.MetaClass);
public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
public java.lang.Object getProperty(java.lang.String);
public void setProperty(java.lang.String, java.lang.Object);
public static void __$swapInit();
static {};
public void super$1$wait();
public java.lang.String super$1$toString();
public void super$1$wait(long);
public void super$1$wait(long, int);
public void super$1$notify();
public void super$1$notifyAll();
public java.lang.Class super$1$getClass();
public java.lang.Object super$1$clone();
public boolean super$1$equals(java.lang.Object);
public int super$1$hashCode();
public void super$1$finalize();
static java.lang.Class class$(java.lang.String);
}
答案 0 :(得分:0)
我能够在Groovy-dev列表的帮助下回答这个问题。
简而言之,它们存在,因为还有其他用例需要它们,但在这种情况下不需要它们,并且在静态情况下删除它们的努力是令人望而却步的。
确切的答案(感谢Cedric Champeau on groovy-dev):
现在答案很简单:这是公共基础设施的工件 我们有动态和静态字节码。 即使你看到的代码不是最理想的,也没有错。在 Groovy,每个表达式都有一个返回值,所以我们需要一个本地 变量(这里是插槽4)来存储它。这个返回值是这样的事实 未使用的是编译器不分析的内容(两者都有) 动态和静态模式),因此不能优化不使用本地 变量。做到这一点会更好,因为它会减少 字节码的大小,使它看起来更像Java,因此制作它 更可能是JIT(因为字节码的大小是 减少了,因为它可以看起来更接近于被认可的模式 JIT),但这样做很难。