从GraalVM到Quarkus系列-A001篇-NativeImage相关的注解

从GraalVM到Quarkus系列

A000篇-忽悠你用GraalVM
A001篇-NativeImage相关的注解
B001篇-NativeImage相关的注解@TargetClass
A002篇-GraalVM中的动态代理


从GraalVM到Quarkus系列-A001篇-NativeImage相关的注解

准备好你的秀发

欢迎来到从入门到放弃系列第A001篇…

今天玩啥?

@AutomaticFeature

代码在com.oracle.svm.core.annotate.AutomaticFeature点击可访问

  1. 一句话描述: 被这个注解标注的类会在NativeImage编译期间被调用

  2. 那这些类是什么? 是实现了org.graalvm.nativeimage.hosted.Feature接口的类(被标注@AutomaticFeature,且实现了Feature接口)这个接口很有意思哈,它所有的方法都是默认空实现1在这里插入图片描述
    在这里插入图片描述

  3. 这些类要干什么? 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){
	}
}
  1. 这段代码在Quarkus中是不存在的哈…是动态生成的,我转译过来了,原本的代码见链接,这段代码在beforeAnalysis方法中,这里Quarkus用了一个动态生成类的框架gizmo,这个后面的章节也会稍微讲一下
  2. 这个方法是用于注册需要在运行时反射的类,A000篇我们知道,NativeImage编译的时候会把用不到的class裁剪掉,但是有时候我们需要用到的类是运行时才用到的,所以需要在静态分析之前,把运行时我们需要用到的类注册一下,这样NativeImage编译器就知道我会在运行时用到这个类,不能把它裁剪掉

怎么用?

  1. 我们先建一个和A000篇一样的项目(我直接复制过来的…)

  2. 建一个实体类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;
        }
    }
    
    
  3. 创建一个Feature接口的实现类,并标注@AutomaticFeature注解,然后Override beforeAnalysis方法

    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();
            }
        }
    }
    
  4. 在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();
                }
            }
        }
    }
    
  5. 根目录mvn clean package把jar编译出来,编译后jar在target目录下
    在这里插入图片描述

  6. 用NativeImage编译下jar,根目录cd target,然后native-image -jar graalvm-a001-1.0-SNAPSHOT.jar --no-server --no-fallback,这时候target目录下会生成编译后的NativeImage程序
    在这里插入图片描述

  7. 直接执行这个程序./graalvm-a001-1.0-SNAPSHOT cbs.demo.domain.Patient,参数是我们让程序反射的类名,然后我们入参一个不存在的类名,可以看到反射是失败的,而且异常是svm包抛出的,这就是graalvm那个小运行时
    在这里插入图片描述

  8. 我们可以对比一下直接执行jar的结果看下区别,他们的运行时是完全不一样的,异常抛出的也不一样
    在这里插入图片描述

  9. 那我们怎么证明这个可执行程序没有用到jvm呢? 项目根目录建个Dockerfile,我们用docker跑一下,用一个不含jvm的环境执行下,可以看到结果和本机执行的结果是一样的
    在这里插入图片描述

  10. 到现在还有个问题,如果不在beforeAnalysis方法中注册会是什么结果?我们注释掉RuntimeReflection.register然后重复上述5,6,7步骤试一下(不要忘了clean package),可以看到不注册这个类,它就会被裁剪掉,在运行时是无法进行反射的
    在这里插入图片描述

小结

  1. 这里只说了反射相关的,beforeAnalysis中还有些运行时初始化啊,代理类啊,资源啊啥的,这些以后再写
  2. 本章节的代码会放到gitee,链接
  3. 其实…graalvm提供了一个参数来实现上述功能:-H:ReflectionConfigurationResources,只不过这种方式不是很灵活,官方文档官方文档,感兴趣可以看下

  1. default修饰且方法体是空的 ↩︎

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值