从GraalVM到Quarkus系列
A000篇-忽悠你用GraalVM
A001篇-NativeImage相关的注解
B001篇-NativeImage相关的注解@TargetClass
A002篇-GraalVM中的动态代理
准备好你的秀发
欢迎来到从入门到放弃系列第A001篇…
今天玩啥?
@AutomaticFeature
代码在com.oracle.svm.core.annotate.AutomaticFeature
点击可访问
-
一句话描述: 被这个注解标注的类会在NativeImage编译期间被调用
-
那这些类是什么? 是实现了
org.graalvm.nativeimage.hosted.Feature
接口的类(被标注@AutomaticFeature
,且实现了Feature
接口)这个接口很有意思哈,它所有的方法都是默认空实现1的
-
这些类要干什么? NativeImage编译器在编译的各个环节都会去回调相对应的方法,我们都知道
default
修饰的接口方法是可以不实现的,所以只要重写我们需要的功能就可以,不用实现所有方法,本章节我们只关注beforeAnalysis
这个方法,定义:/** * Handler for initializations before the static analysis. * * @param access The supported operations that the feature can perform at this time * @since 19.0 */ default void beforeAnalysis(BeforeAnalysisAccess access) { }
为什么这样做?
我从Quarkus源代码中搂一段代码:
private static void registerClass(){
try{
Thread currentThread = Thread.currentThread();
ClassLoader tccl = currentThread.getContextClassLoader();
Class clazz = Class.forName("类名",false,tccl);
Constructor[] constructors = clazz.getDeclaredConstructors();
Method[] methods = clazz.getDeclaredMethods();
Field[] methods = clazz.getDeclaredFields();
...
Class[] carray = new Class[1];
carray[0] = clazz;
RuntimeReflection.register(carray);
...
RuntimeReflection.register(constructors);
...
注册构造函数 方法 属性等...
} catch(Throwable t){
}
}
- 这段代码在Quarkus中是不存在的哈…是动态生成的,我转译过来了,原本的代码见链接,这段代码在
beforeAnalysis
方法中,这里Quarkus用了一个动态生成类的框架gizmo,这个后面的章节也会稍微讲一下 - 这个方法是用于注册需要在运行时反射的类,A000篇我们知道,NativeImage编译的时候会把用不到的class裁剪掉,但是有时候我们需要用到的类是运行时才用到的,所以需要在静态分析之前,把运行时我们需要用到的类注册一下,这样NativeImage编译器就知道我会在运行时用到这个类,不能把它裁剪掉
怎么用?
-
我们先建一个和A000篇一样的项目(我直接复制过来的…)
-
建一个实体类
Patient
package cbs.demo.domain; public class Patient { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
创建一个
Feature
接口的实现类,并标注@AutomaticFeature
注解,然后OverridebeforeAnalysis
方法package cbs.demo.graal; import com.oracle.svm.core.annotate.AutomaticFeature; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; /** * 这个类中的代码是在NativeImage编译class的时候运行的 */ @AutomaticFeature public class NativeImageGen implements Feature { //这里我们直接写死了这个类名,实际项目中可以根据配置文件中配置的包名,扫描包下所有类进行注册 private String className = "cbs.demo.domain.Patient"; @Override public void beforeAnalysis(BeforeAnalysisAccess access) { try { var loader = Thread.currentThread().getContextClassLoader(); var clazz = loader.loadClass(className); //注册该类 RuntimeReflection.register(clazz); }catch (Throwable t){ t.printStackTrace(); } } }
-
在App.java中简单的反射上面注册的类
Patient
,这里我们用参数来传递需要反射的类,是因为NativeImage编译器在静态分析阶段把类似Class.forName
等可以编译时计算出来的类自动注册,所以我们需要用参数传递进来,避免上述情况package cbs.demo; /** * Hello world! */ public class App { public static void main(String[] args) { if (args.length > 0) { try { var clazz = Class.forName(args[0]); System.out.println(clazz.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } }
-
根目录
mvn clean package
把jar编译出来,编译后jar在target
目录下
-
用NativeImage编译下jar,根目录
cd target
,然后native-image -jar graalvm-a001-1.0-SNAPSHOT.jar --no-server --no-fallback
,这时候target目录下会生成编译后的NativeImage程序
-
直接执行这个程序
./graalvm-a001-1.0-SNAPSHOT cbs.demo.domain.Patient
,参数是我们让程序反射的类名,然后我们入参一个不存在的类名,可以看到反射是失败的,而且异常是svm
包抛出的,这就是graalvm那个小运行时
-
我们可以对比一下直接执行jar的结果看下区别,他们的运行时是完全不一样的,异常抛出的也不一样
-
那我们怎么证明这个可执行程序没有用到jvm呢? 项目根目录建个
Dockerfile
,我们用docker跑一下,用一个不含jvm的环境执行下,可以看到结果和本机执行的结果是一样的
-
到现在还有个问题,如果不在
beforeAnalysis
方法中注册会是什么结果?我们注释掉RuntimeReflection.register
然后重复上述5,6,7步骤试一下(不要忘了clean package),可以看到不注册这个类,它就会被裁剪掉,在运行时是无法进行反射的
小结
- 这里只说了反射相关的,
beforeAnalysis
中还有些运行时初始化啊,代理类啊,资源啊啥的,这些以后再写 - 本章节的代码会放到gitee,链接
- 其实…graalvm提供了一个参数来实现上述功能:
-H:ReflectionConfigurationResources
,只不过这种方式不是很灵活,官方文档官方文档,感兴趣可以看下
被
default
修饰且方法体是空的 ↩︎