从GraalVM到Quarkus系列-B001篇-NativeImage相关的注解@TargetClass

从GraalVM到Quarkus系列

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


准备好你的秀发

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

今天玩啥?

@TargetClass

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

  1. 一句话描述: 被这个注解标注的类会在NativeImage编译期间替换方法执行的逻辑而无需改变原有代码

  2. 那这些类是什么? 不用实现接口,不用继承类,只需要标注@TargetClass(要替换方法所在的类.class),在其中重写需要替换的方法

    @TargetClass(FooService.class)
    public final class FooServiceSubstitutions {
    	...要替换的方法
    }
    
  3. 这些类要干什么? 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);
	    }
	}
	...其他内容
}
  1. 这段代码替换了java.lang.reflect.Proxy.classgetProxyConstructor方法逻辑,而且用@TargetElement(onlyWith = JDK11OrLater.class)标注只针对JDK11或以后版本
  2. 方法内容干啥的在动态代理讲哈(不出意外应该是A002篇)

怎么用?

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

  2. 建一个ServiceFooService

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

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

  7. 对比下用JVM运行jar和直接运行原生二进制程序的区别
    在这里插入图片描述

    1. 红色123是我们调用的代码,黄色123JVM执行的结果,蓝色123原生程序执行出来的结果,可见他们执行结果是完全不一样的,蓝色123是我们修改后的代码,其中2和原方法一样是因为我们限制了它的修改只在JDK8或以下版本生效,所以在JDK11的时候是不生效的
    2. 这里还有个@Alias注解,它是引用原始类中字段用的,当我们的替换方法需要使用到原始类中的字段的时候就可以用这个注解再声明一下,就可以直接使用
    3. 相关的注解还有@KeepOriginal,@Delete,@RecomputeFieldValue等等,感兴趣的可以看下

小结

  1. 这个功能的应用场景是啥呢? 就是在NativeImage中很多JDK或者第三方代码逻辑我们是不能在NativeImage中使用的,需要用NativeImage允许的方式重写这部分逻辑,但是又不能直接改写那些源代码,所以用这种编译时打补丁的方式来解决这个问题
  2. 本来这周要写一下NativeImage中的动态代理,但是途中发现有个知识需要较大篇幅,不想写在一块,就单独写出来了
  3. 本篇源代码
  4. 下一篇不出意外就是动态代理部分了(A002篇)
  5. 平时比较忙,产量比较低,最多一周一篇…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值