V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gam2046
V2EX  ›  Java

如何动态修改类的父类/方法调用?

  •  
  •   gam2046 · 2017-08-08 16:48:39 +08:00 · 2937 次点击
    这是一个创建于 2696 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需求

    现在有一些老旧项目需要迁移,针对其中某些方法需要做重定向,例如:

    • 项目中大量使用Class.getResourceAsStream(String)方法
    • 项目中大量继承了某个类OldParent

    由于这些老旧项目通过动态加载的方式运行,那么能否有方法,在运行期间(不是编译期间)达到这样的目的:

    • 拦截所有Class.getResourceAsStream(String)方法调用,由另一个方法提供返回值
    • 所有继承OldParent类的改成继承NewParentNewParent继承自OldParent

    尝试

    尝试过采用 Spring AOP,第一个需求由于无法预知旧项目的Class对象类型,无法拦截。 即项目中可能通过各种Class对象,例如:

    Class.class.getResourceAsStream(...);
    String.class.getResourceAsStream(...);
    "".getClass().getResourceAsStream(...);
    this.getClass().getResourceAsStream(...);
    

    至于第二个需求点,表示完全没有思路,目前在翻看 javassist 文档,不知道是否有解决方案。

    环境限定

    JDK 1.7

    6 条回复    2017-08-09 06:50:51 +08:00
    3pmtea
        1
    3pmtea  
       2017-08-08 18:06:25 +08:00
    自定义 class loader ?
    alexwu
        2
    alexwu  
       2017-08-08 18:33:13 +08:00
    用 Java agent + asm/bcel/javassist 应该可以实现吧
    breadenglish
        3
    breadenglish  
       2017-08-08 18:42:04 +08:00
    为什么要在运行期间,而不是编译期间解决这个问题?
    gam2046
        4
    gam2046  
    OP
       2017-08-08 19:08:21 +08:00
    @breadenglish 算是历史遗留原因,因为旧项目在不同环境下需要 hack 的方法不同。可以理解为新环境下是提供一种模拟环境,供老旧项目运行。

    目前做过这样的尝试,通过 javassist 生成不检查类名安全检查以及 hack 过的 Class 字节码的 ClassLoader 对象,然后试图通过反射修改当前类加载器的父类加载器,以此绕过双亲委派机制,结果并不成功,程序运行直接输出 Exception in thread "main" 且没有退出,仿佛一种假死状态。代码如下:
    ```
    public class JavassistHelp {
    static ClassLoader hackClassLoader = new ClassLoader() {
    private byte[] classBytecode = null;

    private byte[] classBytecode() {
    if (this.classBytecode == null) {
    ClassPool classPool = ClassPool.getDefault();

    try {
    CtClass ordClass = classPool.get("java.lang.Class");
    ordClass.defrost();

    CtMethod ordMethod = ordClass.getDeclaredMethod("getResourceAsStream");
    ordMethod.insertBefore("return new java.io.ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});");

    this.classBytecode = ordClass.toBytecode();
    } catch (NotFoundException e) {
    e.printStackTrace();
    } catch (CannotCompileException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }

    }
    return classBytecode;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    try {

    if ("java.lang.Class".equals(name)) {
    System.err.println("loadClass hack");
    return this.defineClass(name, this.classBytecode(), 0, this.classBytecode().length);
    }

    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
    InputStream is = getClass().getResourceAsStream(fileName);
    if (is == null) {
    return super.loadClass(name);
    }

    byte[] b = new byte[is.available()];
    is.read(b);

    return defineClass(name, b, 0, b.length);
    } catch (IOException e) {
    e.printStackTrace();
    }
    return null;
    }
    };

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    // 通过反射修改父类加载器
    Field parent = ClassLoader.class.getDeclaredField("parent");
    parent.setAccessible(true);

    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(parent, parent.getModifiers() & ~Modifier.FINAL);
    parent.set(JavassistHelp.class.getClassLoader(), JavassistHelp.hackClassLoader);

    System.err.println("load resource is " + String.class.getResourceAsStream("111"));
    }
    }
    ```
    SoloCompany
        5
    SoloCompany  
       2017-08-09 00:23:35 +08:00
    第一个问题,你需要直接置换掉 bootstrapclasspath
    根据 jvm 的 spec,默认的 classloader 实现总是会优先使用 parent loader 以防止系统类被轻易改写

    当然如果你的应用是比如类似 webapp 这类明确说明不符合以上规范的,很简单,把 WebappClassLoader 也置换掉

    第二个问题,你可以先使用编译手段把 OldParent 改成自己想要的样子,然后把 patch 过的 class 和你的老程序 jar 放一起,确保在路径上能够优先加载即可
    erhu
        6
    erhu  
       2017-08-09 06:50:51 +08:00
    为什么用 ordMethod.insertBefore(),直接用 replace() 啊
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1013 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 21:31 · PVG 05:31 · LAX 13:31 · JFK 16:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.