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

哥哥们,我看完一个大佬类似反射特性执行了 String 对象的任意方法,自己是照葫芦画瓢试了报错了

  •  1
     
  •   Parkerz · 265 天前 via Android · 2106 次点击
    这是一个创建于 265 天前的主题,其中的信息可能已经有所发展或是发生改变。
    @Test
        public void likeReflectTest() throws Throwable {
            Class<MethodHandles.Lookup> lookupClass = MethodHandles.Lookup.class;
            Field implLookup = lookupClass.getDeclaredField("IMPL_LOOKUP");
            implLookup.setAccessible(true);
            MethodHandles.Lookup lookup = (MethodHandles.Lookup)implLookup.get(null);
    
            MethodHandle getSum = lookup
                    .in(Phone.class)
                    .findSpecial(
                            Phone.class,
                            "getSum",
                            methodType(int.class, int.class, int.class),
                            Phone.class
                    );
    
            CallSite applyAsInt = LambdaMetafactory.metafactory(
                    lookup,
                    "applyAsInt",
                    methodType(ToIntFunction2.class),
                    methodType(int.class, Phone.class, int.class, int.class),
                    getSum,
                    methodType(int.class, Phone.class, int.class, int.class)
            );
    
            ToIntFunction2 string
                    = (ToIntFunction2) applyAsInt.getTarget().invoke();
    
            int intResult = string.applyAsInt(new Phone(), 1, 0);
    
            System.out.println(intResult);
        }
    

    ToIntFunction2.java 是这样的:

    @FunctionalInterface
    public interface ToIntFunction2 {
       int applyAsInt( Phone value1, int value2, int value3);
    }
    

    Phone.java 的一个方法是这样的:

        int getSum(int param1, int param2) {
           return param1 + param2;
       }
    
    

    最后执行出现了 ClassNotDefFound ,哥哥们,是啥原因呢

    第 1 条附言  ·  262 天前
    仿照别人的代码:( JDK 11 )
    @Test
    public void yumiTest() throws Throwable {
    /**
    * 黑科技第一步,拿到潘多拉魔盒的钥匙 IMPL_LOOKUP
    * 这个 lookup 没有任何校验,你懂得。
    * */
    Class<MethodHandles.Lookup> lookupClass = MethodHandles.Lookup.class;
    Field implLookup = lookupClass.getDeclaredField("IMPL_LOOKUP");
    implLookup.setAccessible(true);
    MethodHandles.Lookup lookup = (MethodHandles.Lookup)implLookup.get(null);

    /**
    * 黑科技第二步,找到自己要的东西
    * */
    MethodHandle coder = lookup
    .in(String.class)
    .findSpecial(
    String.class,
    "coder",
    methodType(byte.class),
    String.class
    );

    /**
    * 黑科技第三步,伪装
    * */

    CallSite applyAsInt = LambdaMetafactory.metafactory(
    lookup,
    "applyAsInt",
    methodType(ToIntFunction.class),
    methodType(int.class, Object.class),
    coder,
    methodType(byte.class, String.class)
    );

    ToIntFunction<String> strCoder
    = (ToIntFunction<String>) applyAsInt.getTarget().invoke();

    int yumiCoder = strCoder.applyAsInt("yumi");

    System.out.println(yumiCoder);
    }
    16 条回复    2024-04-18 11:09:18 +08:00
    hnliuzesen
        1
    hnliuzesen  
       265 天前
    问大语言模型比在这里提问会来的更快

    大语言模型说
    1. findSpecial 用来调用 private 方法,但是 getSum 不是 private 方法
    2. findSpecial 中 getSum 的参数数量与实际不符
    3. LambdaMetafactory 中也是参数不符

    ```
    @Test
    public void likeReflectTest() throws Throwable {
    Class<MethodHandles.Lookup> lookupClass = MethodHandles.Lookup.class;
    Field implLookup = lookupClass.getDeclaredField("IMPL_LOOKUP");
    implLookup.setAccessible(true);
    MethodHandles.Lookup lookup = (MethodHandles.Lookup)implLookup.get(null);

    // Assuming getSum is private, otherwise findVirtual should be used.
    MethodHandle getSum = lookup
    .in(Phone.class)
    .findSpecial(
    Phone.class,
    "getSum",
    methodType(int.class, int.class, int.class),
    Phone.class
    );

    CallSite applyAsInt = LambdaMetafactory.metafactory(
    lookup,
    "applyAsInt",
    methodType(ToIntFunction2.class),
    methodType(int.class, Phone.class, int.class, int.class), // This should match the interface's method signature.
    getSum,
    getSum.type() // This should be the method handle's type.
    );

    ToIntFunction2 func = (ToIntFunction2) applyAsInt.getTarget().invoke();

    int intResult = func.applyAsInt(new Phone(), 1, 0);

    System.out.println(intResult);
    }
    ```
    Parkerz
        2
    Parkerz  
    OP
       265 天前 via Android
    @hnliuzesen 我问了好几个大模型( gpt ,kimi...,也改过楼主说的这个)但是根据他们的建议无一例外全部错了,所以我想问问有没有 v 站的大佬懂这种用法(›´ω`‹ )
    hnliuzesen
        3
    hnliuzesen  
       265 天前
    @Parkerz 你要执行的 Spring 对象的方法是 Spring 管理的对象的方法么?
    darkengine
        4
    darkengine  
       265 天前
    @hnliuzesen 看了代码他要反射的不是 String 类 。。。
    Parkerz
        5
    Parkerz  
    OP
       265 天前 via Android
    @hnliuzesen 这里的对象和 spring 无关。
    Parkerz
        6
    Parkerz  
    OP
       265 天前 via Android
    @darkengine 我看有个 up 用 String 演示的,我用自定义的对象来试发现不行
    rayae
        7
    rayae  
       265 天前
    高版本 jdk 反射要加 VM 参数
    hnliuzesen
        8
    hnliuzesen  
       264 天前
    @darkengine
    @Parkerz
    我眼花看错了
    hnliuzesen
        9
    hnliuzesen  
       263 天前
    试到最后,没有报 ClassNotDefFound ,但是提示 Invalid receiver type interface ToIntFunction2; not a subtype of implementation type class Phone 。
    如果不改 invokedType ,过不去 validateMetafactoryArgs 的验证
    getSum 接收的参数数量也和 applyAsInt 也不一样
    最终是没成功运行

    不知道这段代码的目的是不是想把 getSum 当作是 ToIntFunction2 里 applyAsInt 的实现,如果是的话,感觉可能是生成 CallSite 的方式不合适
    Parkerz
        10
    Parkerz  
    OP
       262 天前
    @hnliuzesen 我也不懂这种用法,听说 FastJSON 源码里都是这种
    hnliuzesen
        11
    hnliuzesen  
       262 天前 via Android
    @Parkerz 这个比反射效率高,感觉主要是让方法引用用的。我建议先用正常的 lambda 表达式来当 ToIntFunction2 的实现来实现你想要的效果,然后用 javap 查看编译出来的 class 文件,里面应该是正确的实现方法
    Aresxue
        12
    Aresxue  
       260 天前
    https://pebble-skateboard-d46.notion.site/Java-7d1e6f877c9d4d02811e1181bc5b361c?pvs=25 看我这篇文章吧,会对方法句柄和 Lambda 有一个更深的了解。
    Parkerz
        13
    Parkerz  
    OP
       260 天前
    @Aresxue 好的,谢谢~
    Parkerz
        14
    Parkerz  
    OP
       260 天前
    @Aresxue 加上这一步就解决了,感谢~ ,但是比较奇怪,为啥 JDK 的 String 中的 coder()方法不需要加这一句就能成功执行到。
    '''MethodHandles.Lookup lookup = TRUSTED.in(xxxx.class);'''
    nieyuanhong
        15
    nieyuanhong  
       256 天前   ❤️ 1
    这段代码应当改为
    ```java
    CallSite applyAsInt = LambdaMetafactory.metafactory(
    lookup.in(Phone.class),
    "applyAsInt",
    methodType(ToIntFunction2.class),
    methodType(int.class, Phone.class, int.class, int.class),
    getSum,
    methodType(int.class, Phone.class, int.class, int.class)
    );
    ```
    因为调用 metafactory 方法时, 方法内部初步生成的 Lambda 字节码会被作为 lookup.in(Phone.class)的内部类加载, 加载时, 会调用方法 java.lang.invoke.InnerClassLambdaMetafactory#generateInnerClass, 具体逻辑形如
    ```java
    //jdk21
    return caller.makeHiddenClassDefiner(lambdaClassName, classBytes, Set.of(NESTMATE, STRONG), lambdaProxyClassFileDumper)
    .defineClass(!disableEagerInitialization, classdata);
    ```
    在 defineClass 中逻辑形如
    ```java
    //jdk21
    Class<?> lookupClass = lookup.lookupClass();
    ClassLoader loader = lookupClass.getClassLoader();
    //...
    ```
    这里的第一句 lookup.lookupClass() 获取的就是 metafactory 的第一个参数 lookup.in(Phone.class)中的 Phone.class, 如果用原始的 lookup 的话, 会获取到 Object.class, 显然 Object 的 classloader 会找不到 Phone.class, 但是可以找到其他和它位于同个 classloader 中的类, 比如 String.class.
    siweipancc
        16
    siweipancc  
       251 天前 via iPhone
    两年前写过这个黑魔法,注意自定义函数接口要声明序列化
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2954 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 14:13 · PVG 22:13 · LAX 06:13 · JFK 09:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.