Java access bean methods with LambdaMetafactory
Asked Answered
R

1

17

my question is strongly related to Explicit use of LambdaMetafactory in that thread, some very good examples are provided to use the LambdaMetafactory to access a static method of a class; however, I wonder what is the equivalent code to access a non static field of an existing bean instance. It seems really hard to find an example and every attempt I performed ended up in non working code.

This is the bean code:

class SimpleBean {
    private Object obj= "myCustomObject";
    private static Object STATIC_OBJECT = "myCustomStaticObject";
    public Object getObj() {
        return obj;
    }
    public void setObj(final Object obj) {
        this.obj = obj;
    }
    public static Object getStaticObj() {
        return STATIC_OBJECT;
    }
    public static void setStaticObj(final Object obj) {
        STATIC_OBJECT = obj;
    }
}

Here a working unit test that successfully access the static method "getStaticObj()":

    @Test
public void accessStaticMethod() throws Throwable
{
    MethodHandles.Lookup caller = MethodHandles.lookup();
    Method reflected = SimpleBean.class.getDeclaredMethod("getStaticObj");
    MethodHandle methodHandle = caller.unreflect(reflected);
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));
    MethodHandle factory = site.getTarget();
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomStaticObject", r.get());
}

Now here my failing attempts to access the non static "getObj()" method:

    @Test
public void accessNonStaticMethodTestOne() throws Throwable
{
    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodHandle methodHandle = caller.bind(simpleBeanInstance, "getObj", MethodType.methodType(Object.class));
    assertEquals("myCustomObject", methodHandle.invoke());

    // This test fails here with exception:
    // java.lang.IllegalArgumentException: not a direct method handle
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));

    MethodHandle factory = site.getTarget();
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}

@Test
public void accessNonStaticMethodTwo() throws Throwable
{

    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();

    Method reflected = SimpleBean.class.getDeclaredMethod("getObj");
    MethodHandle methodHandle = caller.unreflect(reflected);

    // This test fails here with exception:
    // java.lang.invoke.LambdaConversionException: Incorrect number of parameters
    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class),
            methodHandle,
            MethodType.methodType(Object.class));

    MethodHandle factory = site.getTarget();
    factory = factory.bindTo(simpleBeanInstance);
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}


@Test
public void accessNonStaticMethodThree() throws Throwable
{

    SimpleBean simpleBeanInstance = new SimpleBean();

    MethodHandles.Lookup caller = MethodHandles.lookup();

    Method reflected = SimpleBean.class.getDeclaredMethod("getObj");
    MethodHandle methodHandle = caller.unreflect(reflected);

    CallSite site = LambdaMetafactory.metafactory(caller,
            "get",
            MethodType.methodType(Supplier.class),
            MethodType.methodType(Object.class, SimpleBean.class),
            methodHandle,
            MethodType.methodType(Object.class, SimpleBean.class));

    MethodHandle factory = site.getTarget();

    //This test fails here with exception:
    // java.lang.IllegalArgumentException: no leading reference parameter: spike.LambdaBeanAccessAtRuntimeTest$SimpleBean@4459eb14
    factory = factory.bindTo(simpleBeanInstance);
    Supplier r = (Supplier) factory.invoke();
    assertEquals( "myCustomObject", r.get());

}

Every attempt has a different negative result, I really hope someone is abe to help me to have at least one test working fine.

Ruhnke answered 22/12, 2014 at 12:28 Comment(5)
Let's see your attempts. Explain why you tried them and why they didn't work.Exocarp
What's your question?Finding
I added the example code. The exceptions thrown at runtime are explained in the test code itself.Ruhnke
I don't know what you're trying to do, but I'd bet that you're using the wrong tool. LambdaMetafactory is a super-specialized tool for expert users, such as compiler writers.Indecisive
@BrianGoetz One of my company's internal java libraries intensively use reflection to manipulate beans at runtime. After some studies I figured out that most of the reflection base code can be replaced by runtime generated lambda accessors. The advantage is an execution speed which is as fast as precompiled code!Ruhnke
A
24

If you want to bind values to your lamba, you have to include these parameters to the invokedtype signature:

SimpleBean simpleBeanInstance = new SimpleBean();

MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType getter=MethodType.methodType(Object.class);
MethodHandle target=caller.findVirtual(SimpleBean.class, "getObj", getter);
CallSite site = LambdaMetafactory.metafactory(caller,
    "get", // include types of the values to bind:
    MethodType.methodType(Supplier.class, SimpleBean.class),
    getter, target, getter);

MethodHandle factory = site.getTarget();
factory = factory.bindTo(simpleBeanInstance);
Supplier r = (Supplier) factory.invoke();
assertEquals( "myCustomObject", r.get());

Instead of binding a value you may use a Function which takes the bean as argument:

SimpleBean simpleBeanInstance = new SimpleBean();

MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType getter=MethodType.methodType(Object.class);
MethodHandle target=caller.findVirtual(SimpleBean.class, "getObj", getter);
MethodType func=target.type();
CallSite site = LambdaMetafactory.metafactory(caller,
    "apply",
    MethodType.methodType(Function.class),
    func.erase(), target, func);

MethodHandle factory = site.getTarget();
Function r = (Function)factory.invoke();
assertEquals( "myCustomObject", r.apply(simpleBeanInstance));
Allergy answered 22/12, 2014 at 15:49 Comment(5)
Hi Holger, your work fine for all the getters, it even works with primitive parameters. However, I'm able to apply it to setter methods. Based on your example I think that a BiConsumer interface should be used instead of Function, here my attempt: MethodType setter = MethodType.methodType(Void.TYPE, Object.class); MethodHandle target = caller.findVirtual(SimpleBean.class, "setObj", setter); MethodType func = target.type(); CallSite site = LambdaMetafactory.metafactory(caller, "accept",MethodType.methodType(BiConsumer.class), func.generic(), target, func); the factory throws and exceptionRuhnke
While func.generic() is handy for a lot of use cases it doesn’t work well for void methods with the lambda metafactory. So you have to replace func.generic() with func.changeParameterType(0, Object.class)Allergy
@Holger.. Can we use generic functional interface and create a factory pattern with two different classes which implements generic functional interface instead of using LambdaMetaFactory to avoid reflections? Will this worksout?Zr
So that we can reduce lot of complexity with LambdaMetaFactoryZr
@Zr The comment section is not suitable to provide a detailled problem description nor to provide a useful answer. Maybe you want to ask a new question.Allergy

© 2022 - 2024 — McMap. All rights reserved.