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

265 天前
 Parkerz
@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 ,哥哥们,是啥原因呢

2106 次点击
所在节点    Java
16 条回复
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
265 天前
@hnliuzesen 我问了好几个大模型( gpt ,kimi...,也改过楼主说的这个)但是根据他们的建议无一例外全部错了,所以我想问问有没有 v 站的大佬懂这种用法(›´ω`‹ )
hnliuzesen
265 天前
@Parkerz 你要执行的 Spring 对象的方法是 Spring 管理的对象的方法么?
darkengine
265 天前
@hnliuzesen 看了代码他要反射的不是 String 类 。。。
Parkerz
265 天前
@hnliuzesen 这里的对象和 spring 无关。
Parkerz
265 天前
@darkengine 我看有个 up 用 String 演示的,我用自定义的对象来试发现不行
rayae
265 天前
高版本 jdk 反射要加 VM 参数
hnliuzesen
264 天前
@darkengine
@Parkerz
我眼花看错了
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
262 天前
@hnliuzesen 我也不懂这种用法,听说 FastJSON 源码里都是这种
hnliuzesen
262 天前
@Parkerz 这个比反射效率高,感觉主要是让方法引用用的。我建议先用正常的 lambda 表达式来当 ToIntFunction2 的实现来实现你想要的效果,然后用 javap 查看编译出来的 class 文件,里面应该是正确的实现方法
Aresxue
260 天前
https://pebble-skateboard-d46.notion.site/Java-7d1e6f877c9d4d02811e1181bc5b361c?pvs=25 看我这篇文章吧,会对方法句柄和 Lambda 有一个更深的了解。
Parkerz
260 天前
@Aresxue 好的,谢谢~
Parkerz
260 天前
@Aresxue 加上这一步就解决了,感谢~ ,但是比较奇怪,为啥 JDK 的 String 中的 coder()方法不需要加这一句就能成功执行到。
'''MethodHandles.Lookup lookup = TRUSTED.in(xxxx.class);'''
nieyuanhong
256 天前
这段代码应当改为
```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
251 天前
两年前写过这个黑魔法,注意自定义函数接口要声明序列化

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1029641

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX