如何动态修改类的父类/方法调用? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
ntdll
V2EX    Java

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

  •  
  •   ntdll Aug 8, 2017 3517 views
    This topic created in 3186 days ago, the information mentioned may be changed or developed.

    需求

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

    • 项目中大量使用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 replies    2017-08-09 06:50:51 +08:00
    3pmtea
        1
    3pmtea  
       Aug 8, 2017
    自定义 class loader ?
    alexwu
        2
    alexwu  
       Aug 8, 2017
    用 Java agent + asm/bcel/javassist 应该可以实现吧
    breadenglish
        3
    breadenglish  
       Aug 8, 2017
    为什么要在运行期间,而不是编译期间解决这个问题?
    ntdll
        4
    ntdll  
    OP
       Aug 8, 2017
    @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  
       Aug 9, 2017
    第一个问题,你需要直接置换掉 bootstrapclasspath
    根据 jvm 的 spec,默认的 classloader 实现总是会优先使用 parent loader 以防止系统类被轻易改写

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

    第二个问题,你可以先使用编译手段把 OldParent 改成自己想要的样子,然后把 patch 过的 class 和你的老程序 jar 放一起,确保在路径上能够优先加载即可
    erhu
        6
    erhu  
       Aug 9, 2017
    为什么用 ordMethod.insertBefore(),直接用 replace() 啊
    About     Help     Advertise     Blog     API     FAQ     Solana     2647 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 48ms UTC 15:56 PVG 23:56 LAX 08:56 JFK 11:56
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86