首页 > 其他分享 >方法句柄API使用

方法句柄API使用

时间:2024-01-29 22:11:25浏览次数:24  
标签:句柄 Son public API 参数 methodHandle 方法 class

基本使用步骤

第一步,构造要调用方法的MethodType,由返回值类型+参数列表类型组成。

第二步,获取Lookup实例,一般使用MethodHandles类中提供的静态方法获取,最常用的MethodHandles.lookup()

第三步,调用Lookup实例的findXXX方法获取到MethodHandle, 即方法句柄。主要有findConstructorfindStaticfindVirtualfindSpecial等这几个查找方法。

第四步,调用MethodHandle实例的bindTo方法来绑定一个对象,相当于该对象来调用这个查到的方法。注意bindTo会返回一个新的MethodHandle实例。

第五步,调用MethodHandle实例的invokeXXX方法来执行具体查到的方法。主要有invokeinvokeExactinvokeWithArguments

API使用

public class MethodHandleTest {

    public static class GrandFather {

        public void say(String message) {
            System.out.println("GrandFather: " + message);
        }
    }

    public static class Father extends GrandFather {

        public void say(String message) {
            System.out.println("Father: " + message);
        }
    }

    public static class Son extends Father {

        public final void say(String message) {
            System.out.println("Son: " + message);
        }
    }

    /**
     * 支持方法多态调用, 因此像私有方法这种不能重写的方法是不支持的
     * 私有方法需要使用findSpecial方法
     */
    @Test
    public void testFindVirtual() throws Throwable{
        // 第一个参数为参数返回值类型, 其余参数为方法参数类型
        MethodType methodType = MethodType.methodType(void.class, String.class);
        MethodHandle tmp = MethodHandles.lookup()
                .findVirtual(GrandFather.class, "say", methodType);
        /*
         * 绑定对象, 相当于该对象调用这个方法
         * 如果不绑定, 则调用invoke系列方法时, 第一个参数是调用对象, 其余参数是方法参数
         * 注: 会返回一个新的MethodHandle
         */
        MethodHandle methodHandle = tmp.bindTo(new Son());
        // 打印Son: Hello
        methodHandle.invokeWithArguments("Hello");

        methodHandle = tmp.bindTo(new Father());
        // 打印Father: Hello
        methodHandle.invokeWithArguments("Hello");

        methodHandle = tmp.bindTo(new GrandFather());
        // 打印GrandFather: Hello
        methodHandle.invokeWithArguments("Hello");

        // 只支持绑定到Son这个类型的实例(包括子类), 因为使用的Son.class查找
        methodHandle = MethodHandles.lookup()
                .findVirtual(Son.class, "say", methodType)
                .bindTo(new Son());
        methodHandle.invokeWithArguments("Hello");
    }

    /**
     * 1. 这个方法不支持多态
     * 2. 关于方法查找逻辑:
     *    如果第一个参数与第四个参数一样, 则只从指定的这个类中查找方法, 查找不到抛异常
     *    如果不一样, 第四个参数必须是第一个参数的子类型, 则第一个参数是查找的上界,
     *    查找的起点类是第四个参数的父类, 查到方法则停止, 否则继续往父类找。
     * 3. 权限检查:
     *    MethodHandles.LookUp类中存在一个lookupClass属性
     *    在执行findSpecial方法时会进行一个检查, lookupClass必须与第四个参数是同一个类型
     *    从JDK9新增了一个另外, 如果第一个参数是接口, 则可以不一致。
     *    这样子即便直接使用MethodHandles.lookup()也能查找接口中的默认方法。
     *    具体逻辑参见findSpecial方法中的checkSpecialCaller方法
     *
     *    注: MethodHandles.lookup()返回的实例lookupClass属性为调用lookup()方法所在的类
     */
    @Test
    public void testFindSpecial() throws Throwable {
        MethodType methodType = MethodType.methodType(void.class, String.class);
        /*
         * JDK9才有privateLookupIn这个方法, JDK8无
         * 如果直接使用MethodHandles.lookup()获取Lookup实例, 则lookupClass=MethodHandleTest.class
         * 因为lookupClass属性值为执行MethodHandles.lookup()所在的类。
         * 那么lookupClass与第四个参数Son.class不一致, 执行findSpecial会报错
         * 因此使用MethodHandles.privateLookupIn修改lookupClass属性为Son.class
         *
         * 在JDK8中该如何呢, 可通过反射获取LookUp构造方法, 传入lookupClass即可
         * 或者不通过反射, 则只能在Son这个类中使用MethodHandles.lookup()了, 因为此时
         * lookupClass=Son.class, 与第四个参数Son.class一致, 则可以满足检查
         */
        MethodHandle methodHandle = MethodHandles.privateLookupIn(Son.class, MethodHandles.lookup())
                .findSpecial(GrandFather.class, "say", methodType, Son.class)
                .bindTo(new Son());
        /*
         * 尽管绑定的对象是Son的实例
         * 打印Father: Hello, 如果Father中没有此方法, 则打印GrandFather: Hello
         */
        methodHandle.invokeWithArguments("Hello");

        // 查到的方法为Son中的say方法
        methodHandle = MethodHandles.privateLookupIn(Son.class, MethodHandles.lookup())
                .findSpecial(Son.class, "say", methodType, Son.class)
                .bindTo(new Son());
        // 打印Son: Hello
        methodHandle.invokeWithArguments("Hello");
    }
}

invoke系列方法的区别

public class MethodHandleInvokeTest {

    public static class Caculator {

        public Integer sum(Integer num1, Integer num2) {
            return num1 + num2;
        }
    }

    /**
     * invokeExact: 参数和返回值需要精确匹配, 不会自动类型转换, 使用参数声明的静态类型
     * invoke: 参数和返回值会自动类型转换, 比如转型、数字类型提升、拆箱装箱(包装类型,原始类型)
     * invokeWithArguments: invoke方法的升级版本, 支持使用数组作为可变参数传递
     */
    @Test
    public void testInvoke() throws Throwable {
        MethodType methodType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
        MethodHandle tmp = MethodHandles.lookup()
                .findVirtual(Caculator.class, "sum", methodType);
        /*
         * 绑定对象, 相当于该对象调用这个方法
         * 如果不绑定, 则调用invoke系列方法时, 第一个参数是调用对象, 其余参数是方法参数
         * 注: 会返回一个新的MethodHandle
         */
        MethodHandle methodHandle = tmp.bindTo(new Caculator());
        Integer one = 1;
        // ok
        methodHandle.invoke(1, 1);

        // error, 因为参数类型是int, 不是Integer
        assertException(WrongMethodTypeException.class, () -> methodHandle.invokeExact(1, 1));
        // error, 因为返回类型是void, 不是Integer
        assertException(WrongMethodTypeException.class, () -> methodHandle.invokeExact(one, one));
        // ok, 参数类型是Integer, 返回值也是Integer
        Integer result = (Integer)methodHandle.invokeExact(one, one);
        Assert.assertEquals(2, result.intValue());

        Object[] args = new Object[] {1, 1};
        // error, invoke和invokeExact不接受数组作为可变参数, 会认为args是一个参数
        assertException(WrongMethodTypeException.class, () -> methodHandle.invoke(args));
        // ok, invokeWithArguments支持数组作为可变参数, 会当做两个参数看待
        methodHandle.invokeWithArguments(args);
    }

    private <T extends Throwable> void assertException(Class<T> expectedType, Executable executable) {
        try {
            executable.execute();
        } catch (Throwable e) {
            if (expectedType.isInstance(e)) {
                return;
            }
        }
        Assert.fail();
    }

    @FunctionalInterface
    public interface Executable {
        void execute() throws Throwable;
    }
}

在动态代理中使用

在Mybatis的Mapper接口中是可以写默认方法的,这个就是借助方法句柄实现的。

public class MethodHandleWithProxyTest {

    /**
     * 动态代理中借助MethodHandle执行接口中的默认方法
     * 如果直接调用method.invoke(proxy, args)则会无限递归
     * proxy.method() -> InvocationHandler.invoke() -> proxy.method()
     */
    @Test
    public void testExecuteDefaultAtProxy() {
        Mapper proxyInstance = (Mapper) Proxy.newProxyInstance(
                Mapper.class.getClassLoader(),
                new Class<?>[]{Mapper.class},
                new DefaultHandler()
        );
        String sql = "select * from user";
        proxyInstance.executeWithPage(sql, 0 , 10);
    }

    public interface Mapper {
        void execute(String sql);

        default void executeWithPage(String sql, int offset, int size) {
            execute(sql + " limit " + offset + ", " + size);
        }
    }

    public static class DefaultHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            if (method.isDefault()) {
                MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
                /*
                 * 1. 指定lookupClass为方法声明的类, 即接口Mapper.class
                 * 2. 查找接口中对应的默认方法, 限制findSpecial第一个参数和第四个参数都是Mapper.class
                 * 3. 这样lookupClass=findSpecial第四个参数, 可以通过权限检查
                 * 4. 绑定方法到代理对象中, 这样子默认方法如果有调用接口其他方法, 可以让其他方法拥有代理增强
                 *    多态体现
                 */
                Class<?> lookupClass = method.getDeclaringClass();
                MethodHandle methodHandle = MethodHandles.privateLookupIn(lookupClass, MethodHandles.lookup())
                        .findSpecial(lookupClass, method.getName(), methodType, lookupClass)
                        .bindTo(proxy);
                return methodHandle.invokeWithArguments(args);
            } else if("execute".equals(method.getName())) {
                System.out.println(args[0]);
                return null;
            } else {
                throw new UnsupportedOperationException(method.getName());
            }
        }
    }
}

标签:句柄,Son,public,API,参数,methodHandle,方法,class
From: https://www.cnblogs.com/wt20/p/17995477

相关文章

  • 【实战项目】想自己通过C语言编写贪吃蛇吗?先来学习一下什么是WIN32API
    WIN32API前言大家好,很高兴又和大家见面了!!!在开始今天的内容前,咱们先闲聊一下。博主是从2023.8.19号晚上23:28左右正式开始接触C语言,在此之前,我也只是一个对编程一窍不通的小白,我的本科专业是给排水科学与工程,一个就业前景还不错但是不太适合我本人的专业。在经历了一些事情之后,我......
  • WebSocket和RESTful API区别
    1.WebSocket和RESTfulAPI区别WebSocket和RESTfulAPI是用于在客户端和服务器之间进行通信的不同协议。RESTfulAPI(RepresentationalStateTransfer)是一种使用HTTP协议进行通信的架构风格。它基于客户端-服务器模型,通过使用不同的HTTP动词(GET、POST、PUT、DELETE等)对资源进行......
  • SparkSQL无法创建多个Session解决方法
    一、问题现象SparkSQL创建多个session报错,不能创建一个链接,链接Spark自带的数据库derby2024-01-2519:50:59.053[INFO]24/01/2519:50:59INFO!PLExecution!:ExecuteSQL:DROPTABLEIFEXISTSibor_nfsd_instjmport2024-01-2519:51:01.628(INFO]24/01/2519:51:01IN......
  • Apipost中API如何调用本地文件
    近期版本更新中Apipost推出插件管理,可以直接在预、后执行脚本中调用本地的脚本文件导入脚本在「系统设置」—「插件管理」中打开目录将要执行的脚本文件拖入到文件夹下 执行脚本需要获取请求参数:constrequestData=request.request_bodys;在预、后执行脚本输入框中输入......
  • C# 使用自定义特性标注类的方法,直接在当前类中让Main函数调用它
    有的时候我们想要再Main执行一些代码,如果直接在里面写的话,下次再想用的时候就会把之前的代码删掉,好不容易写的代码不想删掉于是我们可以将这些代码写到类文件中,想要执行了,就在Main中调用该类的方法,但是有的时候我们又懒的去Main函数指定的,有没有什么办法能直接在新类中就能指定......
  • 药物不是唯一!多系统萎缩患者运动困难,还有这些方法可以帮助你
    多系统萎缩(MultipleSystemAtrophy,简称MSA)是一种散发的、病因不明的进行性中枢神经系统变性疾病。其临床特征包括自主神经功能障碍、帕金森症状、小脑性共济失调症状以及锥体束征。病理学上,多系统萎缩主要累及纹状体黑质系统、橄榄脑桥小脑系统和自主神经系统等。由于在起病时累及......
  • 如何使用保留可探测字段参数的方法解决视频监控管理平台EasyCVR无法启动的问题
    有用户反馈,在使用EasyCVR时出现启动失败,服务无法使用的情况。收到用户反馈后,技术人员立即开展解决,以下为解决步骤:注:此解决方法为保留hardware_version可被探测的字段参数。1、首先查看报错日志:2、由上图可见,报错为LocalMachineCheckError!本地机器检查错误!随后检查配置文件,是否因......
  • 干货分享 | TSMaster 信号映射的配置方法
    TSMaster信号映射模块可以将数据库变量映射为系统变量,经过映射后的系统变量就等同于数据库中的变量,该系统变量的读写操作就等同于读写数据库变量。其在系统软件中的位置如下图所示:信号映射模块设计的目的,就是为了实现上层应用层逻辑和下层数据库变量的解耦合。如果上层应用层直接操......
  • 如何使用保留可探测字段参数的方法解决视频监控管理平台EasyCVR无法启动的问题
    安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力,也具备接入AI智能分析的......
  • AI 绘画平台难开发,难变现?试试 Stable Diffusion API Serverless 版解决方案
    作者:王佳、江昱、筱姜StableDiffusion模型,已经成为AI行业从传统深度学习时代走向AIGC时代的标志性里程碑。越来越多的开发者借助stable-diffusion-webui(以下简称SDWebUI)能力进行AI绘画领域创业或者业务上新,获得高流量及商业价值,但是面对多客户、高并发的复杂场景,使用原......