Java将字符串变量作为函数传递给回调

时间:2016-07-05 08:12:13

标签: java java-8 vert.x

如何在回调中传递方法类变量?

我有以下代码:

PostsController postController = new PostsController();
router.get("/").handler(postController::index);

但我的控制器将是动态的,我如何传递动态类及其功能。 我尝试了以下代码

Class controller = Class.forName("some.package"+controllerName); //PostsController
Method[] methods = controller.getMethods();
//some loop
Method m = methods[i]; // This is the index method of my controller
router.get(somePath).handler(m); // How can I pass my method m as callback?

PostsController

public class PostsController {

    @Route(method="get", path = "/")
    public void index(RoutingContext routingContext){
        routingContext.response()
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(Json.encodePrettily("{}"));
    }

}

2 个答案:

答案 0 :(得分:2)

最干净的解决方案是使用定义常用功能的interface,例如

public interface Controller {
    void index(RoutingContext routingContext);
}
public class PostsController implements Controller {
    @Route(method="get", path = "/")
    public void index(RoutingContext routingContext){
        routingContext.response()
                .putHeader("content-type", "application/json; charset=utf-8")
                .end(Json.encodePrettily("{}"));
    }
}

Class<? extends Controller> controllerType =
    Class.forName("some.package"+controllers.getString(i))
         .asSubclass(Controller.class);
Controller controller=controllerType.newInstance();
Handler<RoutingContext> h=controller::index;

当然,这不会与基于注释的处理相互作用。动态解决方案看起来像

Class<?> controllerType = Class.forName("some.package"+controllers.getString(i));
MethodHandles.Lookup lookup=MethodHandles.lookup();
Method[] methods = controllerType.getMethods();
Object instance = null;
for (Method m : methods) {
    Route annotation = m.getAnnotation(Route.class);
    if (annotation != null) {
        if(instance == null) instance = controllerType.newInstance();
        Handler<RoutingContext> handler
            =createHandler(lookup, instance, m, RoutingContext.class);
        router.get(annotation.path()).handler(handler);
    }
}

static <T> Handler<T> createHandler(MethodHandles.Lookup lookup,
        Object instance, Method m, Class<T> arg) throws Throwable {

    MethodHandle mh=lookup.unreflect(m);
    MethodType t=mh.type();
    Class<?> receiver=t.parameterType(0);
    if(t.parameterCount()!=2
    || !receiver.isAssignableFrom(instance.getClass())
    || !t.parameterType(1).isAssignableFrom(arg))
      throw new IllegalArgumentException(m+" not suitable for Handler<"+arg.getName()+'>');
    t=t.dropParameterTypes(0, 1);
    return (Handler)LambdaMetafactory.metafactory(lookup, "accept",
        MethodType.methodType(Handler.class, receiver),
        t.changeParameterType(0, Object.class), mh, t)
        .getTarget().invoke(instance);
}

这适用于您未指定的Handler接口的某些假设。如果它被定义为interface Handler<T> extends Consumer<T> {},它将开箱即用。否则,您必须将"accept"调整为功能接口的函数方法的实际名称。如果type参数有一个绑定,你必须更改t.changeParameterType(0, Object.class)行以使用实际绑定(类型擦除后的Handler'参数类型)。

通常,这需要非常小心,因为您没有立即的错误反馈。在最坏的情况下,只有在实际调用回调时(使用某些参数)才会检测到错误。

原则上,您也可以创建一个封装方法的Reflective调用的lambda表达式,即

Handler<RoutingContext> handler=rc -> { try {
    m.invoke(capturedInstance, rc);
} catch(ReflectiveOperationException ex) { ex.printStackTrace(); }};

但这并没有改变你只会很晚才发现错误的事实......

答案 1 :(得分:0)

我能看到的第一件事是你需要创建一个类的实例。

Class controller = Class.forName("some.package"+controllers.getString(i));
Object myInstance = controller.newInstance();

然后你从中得到你的方法,然后调用它:

Method myMethod = ...
myMethod.invoke(myInstance, null);  // or with some params

如果你只需要调用“index”,你也可以让你的控制器实现一个通用接口,然后:

MyInterface myInterface = (MyInterface) myInstance;
myInterface.index();

希望有所帮助。