我是罗纳德,JobRunr 的作者。 JobRunr 是一个后台作业调度库,它使用 SerializedLambda 和 ASM 来分析 Java 8 lambda 并将其转换为后台作业。
最近报告了一个错误,我尝试在 JobRunr 中重现它,以便我可以编写一个测试来防止回归。
有趣的是,在同一个Java版本(17.0.2)上,即使我复制了确切的代码,我也无法重现它。
SerializedLambda
的 implMethodKind
等于 5 (REF_invokeVirtual
)。
然而,在 JobRunr 本身中, generated
SerializedLambda
的 implMethodKind
等于 7 (REF_invokeSpecial
)。
生成
SerializedLambda
的实际代码如下:
public class GeoService {
Logger LOG = LoggerFactory.getLogger(GeoService.class);
public void executeGeoTreeJob(JobContext jobContext, long geoNameId, UserId userId) {
LOG.error("Running: " + geoNameId);
}
public void run() {
LOG.error("Starting job");
UserId userId = new UserId();
userId.setValue("test");
long geoNameId = 1234;
JobLambda jobLambda = () -> executeGeoTreeJob(JobContext.Null, geoNameId, userId);
SerializedLambda serializedLambda = SerializedLambdaConverter.toSerializedLambda(jobLambda);
System.out.println("=======");
System.out.println("serializedLambda " + serializedLambda.getImplMethodKind());
System.out.println("=======");
BackgroundJob.enqueue(() -> executeGeoTreeJob(JobContext.Null, geoNameId, userId));
}
}
SerializedLambda
的 implMethodKind
等于 5 (REF_invokeVirtual
)。
然而,在 JobRunr 本身中, generated
SerializedLambda
的 implMethodKind
等于 7 (REF_invokeSpecial
)。
为什么我会得到不同的
implMethodKind
值?或者,换句话说,我需要对设置/JVM/...执行哪些操作才能获得与示例项目中相同的结果。
更新:
我创建
SerializedLambda
如下:
public class SerializedLambdaConverter {
private SerializedLambdaConverter() {
}
public static <T> SerializedLambda toSerializedLambda(T value) {
if (!value.getClass().isSynthetic()) {
throw new IllegalArgumentException("Please provide a lambda expression (e.g. BackgroundJob.enqueue(() -> myService.doWork()) instead of an actual implementation.");
}
if (!(value instanceof Serializable)) {
throw new JobRunrException("The lambda you provided is not Serializable. Please make sure your functional interface is Serializable or use the JobLambda interface instead.");
}
try {
Method writeReplaceMethod = value.getClass().getDeclaredMethod("writeReplace");
makeAccessible(writeReplaceMethod);
return (SerializedLambda) writeReplaceMethod.invoke(value);
} catch (Exception shouldNotHappen) {
throw shouldNotHappenException(shouldNotHappen);
}
}
}
SerializedLambda
反映了 lambda 表达式是如何编译的,正如这个答案中所解释的,它依赖于编译器。因此,结果不依赖于 Java 运行时版本,而是依赖于包含 lambda 表达式的类所使用的编译器,并且没有运行时选项来更改结果。
除了选择将主体编译为实例方法或接收
static
作为参数的 this
方法之外,编译器还可以自由地将私有实例方法的调用编码为 invokespecial
或 invokevirtual
行为。两者同等有效。
我们可以使用以下程序来自检编码的调用:
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.spi.ToolProvider;
public class LambdaBinary {
public static void main(String[] args) {
ToolProvider.findFirst("javap").ifPresent(new LambdaBinary()::print);
}
private void print(ToolProvider tp) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tp.run(pw, pw, "-v", LambdaBinary.class.getName());
StringBuffer b = sw.getBuffer();
System.out.append(b,
b.lastIndexOf("BootstrapMethods:"),
b.lastIndexOf("InnerClasses:"));
}
}
当使用
javac
版本8到14或使用Eclipse的编译器进行编译时,结果将类似于
BootstrapMethods:
0: #85 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#92 (Ljava/lang/Object;)V
#94 REF_invokeSpecial LambdaBinary.print:(Ljava/util/spi/ToolProvider;)V
#97 (Ljava/util/spi/ToolProvider;)V
当使用 JDK 14 或更高版本的
javac
进行编译时,它会打印类似 的内容
BootstrapMethods:
0: #85 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#92 (Ljava/lang/Object;)V
#94 REF_invokeVirtual LambdaBinary.print:(Ljava/util/spi/ToolProvider;)V
#97 (Ljava/util/spi/ToolProvider;)V
如上所述,
REF_invokeSpecial
和 REF_invokeVirtual
之间的差异由 SerializedLambda
反映,您唯一能做的就是调整代码以相同的方式处理这两个值。
只是为了检查一下,您可以尝试修改您的
.idea/compiler.xml
,这部分:
<bytecodeTargetLevel target="17">
<module name="JobRunr.tests.e2e-elasticsearch-gson.test" target="11" />
<module name="JobRunr.tests.e2e-elasticsearch-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-json-gson.test" target="11" />
<module name="JobRunr.tests.e2e-mariadb-gson.test" target="11" />
<module name="JobRunr.tests.e2e-mariadb-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-mongo-gson.test" target="11" />
<module name="JobRunr.tests.e2e-mongo-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-mysql-gson.test" target="11" />
<module name="JobRunr.tests.e2e-mysql-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-oracle-gson.test" target="11" />
<module name="JobRunr.tests.e2e-oracle-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-postgres-gson.test" target="11" />
<module name="JobRunr.tests.e2e-postgres-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-redis-gson.test" target="11" />
<module name="JobRunr.tests.e2e-redis-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-sqlserver-gson.test" target="11" />
<module name="JobRunr.tests.e2e-sqlserver-jackson.test" target="11" />
<module name="JobRunr.tests.e2e-ui.main" target="11" />
<module name="JobRunr.tests.e2e-ui.test" target="11" />
<module name="JobRunr.tests.e2e-vm-jdk.test" target="11" /> <----- here
</bytecodeTargetLevel>