2017-04-01 67 views
2

我使用ASM更新的类栈地图,但是当ASM getMergedType,会出现以下异常:问题与ASM getMergedType和getCommonSuperClass

了java.lang.RuntimeException:
产生java.io.IOException:资源没有找到IntefaceImplA。

如果没有asm修改类方法,它确实工作正常。
我已经定义了两个接口A和B:IntefaceImplAIntefaceImplB

我的环境的源代码:

IntefaceA.java

public interface IntefaceA { 
    void inteface(); 
} 

IntefaceImplA.java

public class IntefaceImplA implements IntefaceA { 
@Override 
public void inteface() { 

} 
} 

IntefaceImplB.java

public class IntefaceImplB implements IntefaceA { 
@Override 
public void inteface() { 

} 
} 

Test.java

public class Test { 
public IntefaceA getImpl(boolean b) { 
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB(); 
    return a; 
} 

}

Main.java

public class Main { 
public static void main(String args[]) { 
.... 
if (a instance of Test) { 
.. 
... 
} 
} 
} 

我编一个亚军罐子后,并删除IntefaceImplA.class和IntefaceA从jar中手动.class。为什么我想删除这些类文件,因为春天总是喜欢做这个东西。

runner jar可以在没有ASM的情况下正常运行,但使用asm会发生异常。因为asm想要为IntefaceImplA和IntefaceImplB获取MergeType,但IntefaceImplA被我删除。 调查后,ASM ClassWriter源代码,我发现下面的代码:

protected String getCommonSuperClass(String type1, String type2) 
{ 
    ClassLoader classLoader = this.getClass().getClassLoader(); 
    Class c; 
    Class d; 
    try { 
     c = Class.forName(type1.replace('/', '.'), false, classLoader); 
     d = Class.forName(type2.replace('/', '.'), false, classLoader); 
    } catch (Exception var7) { 
     throw new RuntimeException(var7.toString()); 
    } 

    if(c.isAssignableFrom(d)) { 
     return type1; 
    } else if(d.isAssignableFrom(c)) { 
     return type2; 
    } else if(!c.isInterface() && !d.isInterface()) { 
     do { 
      c = c.getSuperclass(); 
     } while(!c.isAssignableFrom(d)); 

     return c.getName().replace('.', '/'); 
    } else { 
     return "java/lang/Object"; 
    } 
} 

其实,我删除了相关的类文件,类加载器无法找到类。但程序没有正常工作。
我应该增强对getCommonSuperClass方法的覆盖,如果发生异常,那么返回java/lang/Object?这很有趣

+0

1.)为什么“使用ASM更新类堆栈映射”?没有理由更新堆栈映射,除非您执行了其他代码转换。但是如果您执行其他代码转换,则这些转换是ASM的主要用途,而不是更新堆栈映射。你应该说一些关于实际的目标。 2)为什么你会删除仍然被代码引用的类? “因为春天总是喜欢做这个东西”并不是一个有意义的解释。 – Holger

+0

因为我需要在运行时插入一些代码。 –

+0

由于在使用tomcat作为嵌入容器时,弹簧引导删除了代码以减小jar大小。 为什么“使用ASM更新类堆栈映射”?---我需要在运行时插入一些代码。 –

回答

2

通常,重写getCommonSuperClass以使用不同的策略,例如,不加载类,是一个有效的用例。作为it’s documentation状态:

此方法的默认实现负载两个给定类和使用方法java.lang.Class类找到共同的超类。可以通过其他方式重写计算这种常见的超类型,特别是不需要实际加载任何类,或者考虑当前由该ClassWriter生成的类,当然由于它正在构建中而不能被加载。

除了这两个参数都是您正在构建(或实质上正在改变)的类的可能性之外,代码转换工具的上下文可能不是类最终将运行的上下文,所以在这种情况下,他们不必通过Class.forName来访问。由于Class.forName使用调用者的上下文(即ASM的ClassWriter),即使在使用ASM的代码的上下文中(如果涉及不同的类加载器),ASM甚至有可能无法访问该类。

另一个有效的方案是通过使用已有的元信息来解析请求,而无需实际加载类,从而获得更高效的方法。

但是,当然,这不是一个有效的决议,只是返回"java/lang/Object"。虽然这确实是每个参数的常见超类型,但它并不一定是代码的正确类型。留在你的榜样,

public IntefaceA getImpl(boolean b) { 
    IntefaceA a = b ? new IntefaceImplA() : new IntefaceImplB(); 
    return a; 
} 

IntefaceImplAIntefaceImplB公用超类型,不仅需要核实分配任一类型以它的有效性,这也是条件表达式的结果类型,它必须是可分配给方法的返回类型。如果您使用java/lang/Object作为常用超级类型,验证程序将拒绝该代码,因为它不能指定给IntefaceA

由于该类型与方法的返回类型相同,所以原始堆栈图很可能会报告IntefaceA作为常见的超级类型,因此即使不加载类型也可以被认为是可分配的。无论是IntefaceImplA还是IntefaceImplB,测试都可以分配给该指定的通用类型,可能会推迟到实际加载这些类型的点,并且由于您说过,您将删除IntefaceA,这种情况永远不会发生。

其声明的返回类型不存在的方法根本无法工作。你的观察的唯一解释是“没有程序正常工作”,就是说,在你的测试过程中,这个方法从来没有被调用过。您最有可能通过删除正在使用的课程在您的软件中创建了一个定时炸弹。

目前尚不清楚你为什么这么做。你的解释“春天总是喜欢做这个东西”远远不能理解。


但是,您可以使用重写方法获得与未修改代码相同的行为。它只是不返回java/lang/Object。你可以使用

@Override 
protected String getCommonSuperClass(String type1, String type2) { 
    if(type1.matches("IntefaceImpl[AB]") && type2.matches("IntefaceImpl[AB]")) 
     return "IntefaceA"; 
    return super.getCommonSuperClass(type1, type2); 
} 

当然,如果你删除了更多的类文件,你必须添加更多的特殊情况。

完全不同的方法是不使用COMPUTE_FRAMES选项。这个选项意味着ASM将从头开始重新计算所有的堆栈映射框架,这对于懒惰的程序员来说非常有用,但是如果你只是对现有的类进行很少的代码转换,就意味着很多不必要的工作,当然,这就需要有一个工作getCommonSuperClass方法。

如果没有这个选项,ClassWriter只会复制ClassReader报告的帧,所以所有未更改的方法也将具有不变的堆栈映射。您将不得不关心您更改代码的方法,但对于很多典型的代码转换任务,您仍然可以保留原始框架。例如。如果您只是将方法调用重定向到与签名兼容的目标或注入日志语句,而这些语句将堆栈保持在它们之前的相同状态,则仍然可以保留自动发生的原始帧。请注意0​​的存在,它允许更高效地转移不改变的方法。

只有当您更改分支结构或插入带分支的代码时,您必须关心框架。但即使如此,通常值得学习如何做到这一点,因为自动计算安静而昂贵,而在进行代码转换时通常已经有了必要的信息。