Как получить доступ к нестатическому методу в динамическом классе с помощью LambdaMetafactory во время выполнения - PullRequest
0 голосов
/ 05 ноября 2018

Я пытаюсь использовать LambdaMetafactory для замены отражения, но у меня есть проблема. Если я использую определенный класс, он работает хорошо, вот так:

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType type = MethodType.methodType(ResponseMsg.class,Map.class);

        MethodHandle mh = lookup.findVirtual(TestService.class,"testMethod",type);

        TestService ins = TestService.getInstance();

        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,TestService.class),
                type.generic(), mh, type).getTarget();

        factory.bindTo(ins);

        Function lambda = (Function) factory.invokeExact(ins);

Но если я использую Class<?> для замены определенного класса, тогда он не будет работать, так:

    public static Function generateLambda(@NotNull Class<?> cls,@NotNull String method) {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodType type = MethodType.methodType(RETURN_TYPE,PARM_TYPE);

    try {
        MethodHandle mh = lookup.findVirtual(cls,method,type);
        Object instance = getInstance(cls);
        if(instance == null) {
            return null;
        }
        MethodHandle factory = LambdaMetafactory.metafactory(
                lookup, "apply", MethodType.methodType(Function.class,cls),
                type.generic(), mh, type).getTarget();

        factory.bindTo(cls.cast(instance));

        return (Function) factory.invokeExact(cls.cast(instance));
    } catch (Throwable e) {
        logger.error("get Function fail, cause :" ,e);
        return null;
    }
}

Вот исключение:

java.lang.invoke.WrongMethodTypeException: expected (TestService)Function but found (Object)Function
    at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:298)
    at java.lang.invoke.Invokers.checkExactType(Invokers.java:309)
    at com.utils.cache.ClassUtils.generateLambda(ClassUtils.java:182)

Строка 182:

return (Function) factory.invokeExact(cls.cast(instance));

Я знаю, что только статический метод может решить эту проблему, но я хочу знать, есть ли другой способ решить эту проблему, не меняя нестатический на статический.

Вот getInstance:

 private static Object getInstance(@NotNull Class<?> cls) {
        try {
            Method getInstanceMethod = cls.getDeclaredMethod("getInstance");
            return getInstanceMethod.invoke(null);
        } catch (Exception e) {
            logger.error("get instance fail, cause :" ,e);
            return null;
        }
    }

В этом методе я использую рефлексию, чтобы найти статический метод getInstance в классе, и возвращаю экземпляр, это просто простой синглтон.

1 Ответ

0 голосов
/ 05 ноября 2018

Проблема в том, что вы используете

factory.bindTo(ins);
Function lambda = (Function) factory.invokeExact(ins);

соответственно.

factory.bindTo(cls.cast(instance));
return (Function) factory.invokeExact(cls.cast(instance));

При вызове bindTo создается MethodHandle, первый параметр которого связан с указанным экземпляром объекта, однако вы игнорируете новый MethodHandle. Поэтому вам нужно снова указать экземпляр в качестве аргумента при вызове несвязанного дескриптора.

Для этого вызова тип времени компиляции имеет значение. В первом примере тип аргумента во время компиляции является правильным, поэтому вызов имеет правильную подпись (TestService)Function.

Во втором примере тип времени компиляции instance равен Object, следовательно, подпись, скомпилированная в байт-код, будет (Object)Function, что не является точным соответствием. Использование cls.cast(…) не помогает, так как при этом выполняется проверка времени выполнения и утверждение, что универсальные типы совпадают, если вы использовали здесь переменную типа, но оба эти значения не имеют отношения к байтовому коду вызова invokeExact .

У вас есть два варианта. Вместо этого вы можете просто использовать invoke, что позволяет преобразовывать типы во время вызова (жертвуя производительностью в битах)

// unused factory.bindTo call omitted
return (Function) factory.invoke(instance); // noneffective cls.cast omitted

или вы измените код, чтобы сделать то, что кажется изначально предназначенным, связать первый аргумент перед вызовом:

factory = factory.bindTo(instance);
return (Function)factory.invokeExact();

, поскольку для дескриптора метода с предварительно связанной привязкой аргумент не требуется, у вас снова точный вызов (bindTo не является полиморфным сигнатурой, следовательно, будет проверять тип аргумента только во время выполнения).

Вы также можете написать это как однострочник

return (Function)factory.bindTo(instance).invokeExact();
...