3

How can I create proxy objects for SAM/functional interfaces using LambdaMetaFactory

ie. equivalent of public static Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)

Eg. I have multiple factory interfaces

interface X{
// members
// methods
}

interface Y{
// members
// methods
}
public interface Factory1{
  X get(String name);
}
public interface Factory2{  
Y get(String name);
}
.
.
.
public interface FactoryN{
someclassOrInterface get(String name);
}

and I want to generate the factory proxies at runtime tied to some prebuilt bean container e.g.

public Object getFactoryBean(String name){ 
return beanContainer.get(name); 
}

something similar to

org.springframework.beans.factory.config.ServiceLocatorFactoryBean

but built on LambdaMetaFactory. I tried following code but gets an exception

static <T> T  getFactory(Class<T> factoryClass) {
    T factory =null;
    try {
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    Class<?> beanType = factoryClass.getMethod("get", String.class).getReturnType();
    final CallSite site = LambdaMetafactory.metafactory(lookup,
                    "get",
                    MethodType.methodType(factoryClass, String.class),
                    MethodType.methodType(beanType),
                    lookup.findStatic(ReflectionUtil.class, "getFactoryBean", MethodType.methodType(Object.class, String.class)),
                    MethodType.methodType(beanType));
    factory = (T) site.getTarget().invoke();
    } catch(Throwable e) {
      e.printStackTrace();
    }
    return factory;
}
 public static Object getFactoryBean(String beanName) {
     return beanMap.get(beanName);
  }
java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(String)Factory1 to ()Object
    at java.lang.invoke.MethodHandle.asTypeUncached(MethodHandle.java:775)
    at java.lang.invoke.MethodHandle.asType(MethodHandle.java:761)
    at java.lang.invoke.Invokers.checkGenericType(Invokers.java:321)

Thanks in advance.

S M
  • 81
  • 7

1 Answers1

3

Your code specifies a String argument for the invokedType argument instead of the samMethodType and instantiatedMethodType arguments. This would be the right thing if you want to capture a String value and implement a functional signature without arguments.

But you consistently show interfaces with a String argument and call invoke() without arguments. Therefore, the code should look like

static <T> T getFactory(Class<T> factoryClass) {
    T factory =null;
    try {
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        Class<?> beanType = factoryClass.getMethod("get", String.class).getReturnType();
        final CallSite site = LambdaMetafactory.metafactory(lookup,
                        "get",
                        MethodType.methodType(factoryClass),
                        MethodType.methodType(beanType, String.class),
                        lookup.findStatic(ReflectionUtil.class, "getFactoryBean",
                            MethodType.methodType(Object.class, String.class)),
                        MethodType.methodType(beanType, String.class));
        factory = (T)site.getTarget().invoke();
    } catch(Throwable e) {
      e.printStackTrace();
    }
    return factory;
}

which can be simplified to

static <T> T getFactory(Class<T> factoryClass) {
    try {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        Class<?> beanType = factoryClass.getMethod("get", String.class).getReturnType();
        MethodHandle target = lookup.findStatic(ReflectionUtil.class, "getFactoryBean",
            MethodType.methodType(Object.class, String.class));
        MethodType funcType = target.type().changeReturnType(beanType);
        return (T)LambdaMetafactory.metafactory(
            lookup, "get", MethodType.methodType(factoryClass), funcType, target, funcType)
            .getTarget().invoke();
    } catch(Throwable e) {
      e.printStackTrace();
      return null; // rethink this error handling
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    there is special code in the VM to optimize MethodHandles/Lookup, but only when these are static final. I think there was even a question about this a while ago – Eugene Feb 18 '20 at 10:02
  • 1
    @Eugene more than one question, I know them. But they aren’t really relevant for `LambdaMetafactory` uses like this. The generated classes perform as good as pre-compiled classes. – Holger Feb 18 '20 at 10:05
  • @Holger thanks that worked well. I can see my syntax was quite off. Is there any good resource which explains `LambdaMetafactory` a little bit in more detail. – S M Feb 18 '20 at 23:03
  • 1
    The [class documentation of `LambdaMetafactory`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html) and the [documentation of the `metafactory` method](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html#metafactory(java.lang.invoke.MethodHandles.Lookup,java.lang.String,java.lang.invoke.MethodType,java.lang.invoke.MethodType,java.lang.invoke.MethodHandle,java.lang.invoke.MethodType)) are quite exhaustive. – Holger Feb 19 '20 at 08:15