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

Java pass a string variable as a function in a callback

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

我有以下代码:

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

但是我的controller会是dynamic的,我怎么传dynamicclass和它的函数。 我尝试了以下代码

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?

帖子控制器

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("{}"));
    }

}

我能看到的第一件事是,您需要创建 class 的实例。

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

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

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

如果你只需要调用"index",你也可以让你的controller实现一个通用的接口,然后:

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

希望对您有所帮助。

最干净的解决方案是使用 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" 调整为您的功能接口的功能方法的实际名称。如果类型参数有边界,则必须更改 t.changeParameterType(0, Object.class) 行以使用实际边界(类型擦除后 Handler 的参数类型)。

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

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

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

但这并不能改变您将很晚才检测到错误的事实……