Befehle per Annotation
Eine Befehlstabelle aus annotierten Methoden: finden, ihre Parameter für Hilfetexte beschreiben und per Namen aufrufen. Das Muster hinter Routern, CLI-Werkzeugen und RPC-Endpunkten.
Modell
Methoden, die einen Namen tragen
Eine Methodenannotation mit einem Wert. Die Klasse bleibt eine gewöhnliche Klasse, die du ohne Befehlstabelle testen kannst.
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Command { String value(); }
scala
class Shell {
@Command("greet") def greet(name: String): String = s"Hello $name"
@Command("shout") def shout(text: String): String = text.toUpperCase
def helper(): Unit = ()
}
Tabelle
Von Methoden zu einer Map
methods enthält geerbte und überschriebene Methoden genau einmal, und findAnnotation sieht auch Annotationen an einer Interface-Methode.
scala
val shell = TypeResolver.resolve(classOf[Shell])
val commands = shell.methods.flatMap { method =>
Option(method.findAnnotation(classOf[Command])).map(command => command.value() -> method)
}.toMap
commands.toSeq.sortBy(_._1).foreach { case (name, method) =>
println(s"$name(${method.parameters.map(p => s"${p.name}: ${p.parameterType.name}").mkString(", ")})")
}
Ausgabe
greet(name: String)
shout(text: String)
Aufruf
Per Namen, mit geprüften Argumenten
Prüfe die Anzahl der Argumente gegen parameters, bevor du aufrufst; die Parametertypen sagen dir, wie Texteingaben umzuwandeln sind.
scala
def run(target: Shell, name: String, args: String*): Any =
val method = commands.getOrElse(name, sys.error(s"Unknown command $name"))
require(method.parameters.length == args.length, s"$name takes ${method.parameters.length} arguments")
method.invoke(target, args*)
println(run(new Shell, "greet", "Ada")) // Hello Ada
Die Tabelle einmal bauen
Auflösen und Durchsuchen passieren einmal; danach ist jeder Aufruf ein Map-Zugriff und ein invoke.