从GraalVM到Quarkus系列
A000篇-忽悠你用GraalVM
A001篇-NativeImage相关的注解
B001篇-NativeImage相关的注解@TargetClass
A002篇-GraalVM中的动态代理
准备好你的秀发
欢迎来到从入门到放弃系列第B001篇…
今天玩啥?
@TargetClass
代码在com.oracle.svm.core.annotate.TargetClass
点击可访问
-
一句话描述: 被这个注解标注的类会在NativeImage编译期间替换方法执行的逻辑而无需改变原有代码
-
那这些类是什么? 不用实现接口,不用继承类,只需要标注
@TargetClass(要替换方法所在的类.class)
,在其中重写需要替换的方法@TargetClass(FooService.class) public final class FooServiceSubstitutions { ...要替换的方法 }
-
这些类要干什么? NativeImage编译器在把class字节码编译成可执行程序时,会将类中标注了
@Substitute
注解的方法去替换相同方法签名的原始方法,原始类和修改类如下:/** * 原始类 */ public class FooService { public String foo(String foo) { return "foo:" + foo); } } /** * 替换方法的类 */ @TargetClass(FooService.class) public final class FooServiceSubstitutions { /** * 方法签名需要和原方法一致 * @param foo * @return */ @Substitute public String foo(String foo) { System.out.println("Substitute改写方法体"); return "Substitutions:" + foo); } }
为什么这样做?
我从GraalVM源代码中搂一段代码:
@TargetClass(java.lang.reflect.Proxy.class)
final class Target_java_lang_reflect_Proxy {
...其他内容
@Substitute
@TargetElement(onlyWith = JDK11OrLater.class)
@SuppressWarnings("unused")
private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) {
final Class<?> cl = ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(interfaces);
try {
final Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
if (!Modifier.isPublic(cl.getModifiers())) {
cons.setAccessible(true);
}
return cons;
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
...其他内容
}
- 这段代码替换了
java.lang.reflect.Proxy.class
的getProxyConstructor
方法逻辑,而且用@TargetElement(onlyWith = JDK11OrLater.class)
标注只针对JDK11或以后版本 - 方法内容干啥的在动态代理讲哈(不出意外应该是A002篇)
怎么用?
-
我们先建一个和A000篇一样的项目(我直接复制过来的…)
-
建一个Service
FooService
package cbs.demo.service; import cbs.demo.domain.Patient; /** * 原始类 */ public class FooService { private Patient patient; public FooService(Patient patient) { this.patient = patient; } public FooService() { } public String foo(String foo) { return "foo:" + (patient == null ? foo : patient.getName()); } public String foo8(String foo) { return "foo8:" + (patient == null ? foo : patient.getName()); } public String foo11(String foo) { return "foo11:" + (patient == null ? foo : patient.getName()); } }
-
创建一个标注
@TargetClass
的类FooServiceSubstitutions
package cbs.demo.graal; import cbs.demo.domain.Patient; import cbs.demo.service.FooService; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.JDK11OrEarlier; import com.oracle.svm.core.jdk.JDK8OrEarlier; /** * 替换方法的类 */ @TargetClass(FooService.class) public final class FooServiceSubstitutions { /** * 引用原类中的字段 */ @Alias private Patient patient; /** * 方法签名需要和原方法一致 * @param foo * @return */ @Substitute public String foo(String foo) { System.out.println("\nSubstitute改写方法体"); return "Substitutions:" + (patient == null ? foo : patient.getName()); } @Substitute @TargetElement(onlyWith = JDK8OrEarlier.class) public String foo8(String foo) { System.out.println("\nSubstitute JDK8改写方法体"); return "Substitutions8:" + (patient == null ? foo : patient.getName()); } @Substitute @TargetElement(onlyWith = JDK11OrEarlier.class) public String foo11(String foo) { System.out.println("\nSubstitute JDK11改写方法体"); return "Substitutions11:" + (patient == null ? foo : patient.getName()); } }
-
在App.java中调用上述Service
package cbs.demo; import cbs.demo.domain.Patient; import cbs.demo.service.FooService; public class App { public static void main(String[] args) { var patient = new Patient(); patient.setId(1); patient.setName("tony"); var foo = new FooService(patient); var result = foo.foo("FOO"); System.out.println(result); var result8 = foo.foo8("FOO"); System.out.println(result8); var result11 = foo.foo11("FOO"); System.out.println(result11); } }
-
根目录
mvn clean package
把jar编译出来,编译后jar在target
目录下
-
用NativeImage编译下jar,根目录
cd target
,然后native-image -jar graalvm-b001-1.0-SNAPSHOT.jar --no-server --no-fallback
,这时候target目录下会生成编译后的NativeImage程序
-
对比下用JVM运行jar和直接运行原生二进制程序的区别
- 红色123是我们调用的代码,黄色123是JVM执行的结果,蓝色123是原生程序执行出来的结果,可见他们执行结果是完全不一样的,蓝色123是我们修改后的代码,其中2和原方法一样是因为我们限制了它的修改只在JDK8或以下版本生效,所以在JDK11的时候是不生效的
- 这里还有个
@Alias
注解,它是引用原始类中字段用的,当我们的替换方法需要使用到原始类中的字段的时候就可以用这个注解再声明一下,就可以直接使用 - 相关的注解还有
@KeepOriginal
,@Delete
,@RecomputeFieldValue
等等,感兴趣的可以看下
小结
- 这个功能的应用场景是啥呢? 就是在NativeImage中很多JDK或者第三方代码逻辑我们是不能在NativeImage中使用的,需要用NativeImage允许的方式重写这部分逻辑,但是又不能直接改写那些源代码,所以用这种编译时打补丁的方式来解决这个问题
- 本来这周要写一下NativeImage中的动态代理,但是途中发现有个知识需要较大篇幅,不想写在一块,就单独写出来了
- 本篇源代码
- 下一篇不出意外就是动态代理部分了(A002篇)
- 平时比较忙,产量比较低,最多一周一篇…