Auch das Interface Command hat eine Überarbeitung erfahren. Es definiert nun die folgenden vier Methoden:

Neben den gelisteten Änderungen wurden, wie die Methodensignaturen bereits erkennen lassen, die Methodenargumente mit Generics versehen. Die bisher existierende Klasse AbstractCommand existiert künftig nicht mehr. Einfache Commands müssen daher künftig das Interface selbst implementieren. Im nahezu allen Fällen empfiehlt es sich jedoch stattdessen, von der weiter unten beschriebenen Klasse InjectedCommand zu erben, um die Vorteile der Dependency-Injection zu nutzen.

Die Parameter-Injection bezieht sich auf alle Objekte, die aus den commands.properties befüllt werden. Diese wurden in früheren Versionen durchnummeriert und mithilfe der Methode com.levigo.util.swing.action.AbstractCommand.getCommandParameter(int, String) zur Verfügung gestellt.

Der neue Parameter Injection Mechanismus geht einen wesentlich komfortableren Weg: Die Nummerierung in commands.properties wurde durch Benennung der einzelnen Parameter ersetzt und für die zur Verfügung gestellten Objekte erfolgt nun eine automatische Typkonvertierung und Befüllung der Klassenvariablen.

Das folgende Beispiel zeigt die Implementation eines Commands, das einen Parameter aus den commands.properties nutzt. Es muss dazu lediglich eine Annotation angebracht werden, die festlegt, dass das Feld aus einem Properties-Parameter befüllt werden soll. Die Zuweisung der korrekten Instanz an someStringParameter erfolgt dann automatisch durch die Command Factory. Um zu verdeutlichen, dass die Parameter-Injection auch mit solchen Commands funktioniert, die nicht von InjectedCommand erben, wird im Beispiel das Interface direkt implementiert.

public class DemoCommand implements Command {

  @Parameter
  private String someStringParameter;

  @Override
  public void execute(Collection<Object> args) {

    // create dialog
    final JDialog dialog = new JDialog();
    dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    // use injected parameter and argument
    dialog.add(new JLabel("parameter: " + someStringParameter));

    // show dialog on screen (in this case, we're already on EDT)
    dialog.pack();
    dialog.setVisible(true);

  }

  @Override
  public boolean canExecute(Collection<Object> args) {
    // can always execute!
    return true;
  }

  @Override
  public boolean isSelected(Collection<Object> args) {
    // no meaningful selection state -- therefore never selected
    return false;
  }

  @Override
  public boolean isAvailable() {
    // always available
    return true;
  }

}

Damit das Command zur Verfügung steht und mit dem korrekten Parameter befüllt wird, müssen in der commands.properties folgende Inhalte hinzugefügt werden:

# specify which class to use
demoCommand=com.levigo.jadice.demo.commands.DemoCommand
# parameter to be injected
demoCommand.param.someStringParameter=this is the injected parameter string.

Für Parameter, die auf diese Weise angefordert werden, stellt das Framework sicher, dass diese nach Instanziierung der Klasse bereitstehen.

Die Annotation @Parameter kann, wie das Beispiel bereits zeigt, an einer Klassenvariablen angebracht werden, unabhängig von deren Sichtbarkeits-Modifier. Zu beachten ist jedoch an dieser Stelle, dass in manchen Security-Kontexten (zum Beispiel innerhalb von Applets) nur public möglich ist, da der Aufruf von Field.setAccessible(true) in einer SecurityException resultiert.

Wie es auch in anderen Injection-Frameworks konvention ist, besteht auch die Möglichkeit, die Annotation @Parameter an einer setter-Methode anzubringen. Der erwartete Parametername entspricht dann dem Methodennamen ohne das set-Prefix und mit dem ersten Buchstaben klein geschrieben. Bezüglich der Methodensichtbarkeit gelten die gleichen Regeln wie für Klassenvariablen.

Für Fälle, in denen der Parametername in commands.properties nicht dem Variablennamen entsprechen oder aus dem Methodennamen generiert werden soll, besteht die Möglichkeit, die Annotation @Parameter mit einem optionalen Argument name zu versehen, das den in der Properties-Datei verwendeten Namen definiert. Des Weiteren existiert ein Annotations-Argument optional, mit dem definiert werden kann, dass das Fehlen der Parameter-Definition in commands.properties nicht zu einem Fehler führen soll. Das Command wird dann trotzdem instanziiert und annotierte Felder behalten ihren in der Klasse definierten Default-Wert.

Die Parameter-Injection funktioniert für folgende Datentypen: String, Color, enums und die Basis-Datentypen wie int, float oder boolean.

Die zweite Möglichkeit, Objekte in Kommandos zu injizieren, ist die Argument-Injection. Während Parameter aus den Properties stammen und in jegliches Command aufgenommen werden können, beziehen sich Argumente auf im Context vorhandene Objekte, werden aus diesem heraus injiziert, und können ausschließlich in solchen Kommandos verwendet werden, die InjectedCommand erweitern.

Das folgende Beispiel erweitert die bereits vorgestellte Klasse DemoCommand um eine Argument-Injection. Die bestehende Parameter-Injection ist nach wie vor enthalten, um zu zeigen, dass beides gleichzeitig möglich ist. Da die Klasse jetzt von InjectedCommand erbt, ändert sich auch deren Implementation. Lediglich die execute()-Methode muss noch zur Verfügung gestellt werden – für die restlichen Methoden des Interface existieren Default-Implementationen. Doch auch für execute() ergibt sich eine Änderung: Der Methode wird keine Collection von Argumenten mehr übergeben, da diese über die Funktionalität der Superklasse verarbeitet und injiziert werden. Insgesamt muss das benötigte Feld also lediglich mit @Argument annotiert werden und es wird daraufhin durch die Funktionalität aus InjectedCommand automatisch mit den entsprechenden Kontext-Argumenten befüllt.

public class DemoCommand extends InjectedCommand {

  @Argument
  private String someStringArgument;

  @Parameter
  private String someStringParameter;

  @Override
  public void execute() {

    // create dialog
    final JDialog dialog = new JDialog();
    dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    // use injected parameter and argument
    dialog.add(new JLabel("parameter: " + someStringParameter + " -- argument: " + someStringArgument));

    // show dialog on screen (in this case, we're already on EDT)
    dialog.pack();
    dialog.setVisible(true);

  }
}

Im Beispiel wird ein String als Argumenttyp verwendet, es könnte aber auch jedes andere Objekt abgefragt werden, sofern es im Context vorliegt.

In realen Szenarien kann es leicht vorkommen, dass der Kontext mehrere Objekte desselben Typs enthält. In solchen Fällen wird jeweils das erste gefundene Objekt dieses Typs injiziert. Alternativ dazu ist es möglich, sämtliche Objekte eines Typs abzufragen. In diesem Fall werden alle gefundenen Objekte in einer Collection des entsprechenden Typs zurückgegeben. So werden beispielsweise mit dem Code-Fragment

@Argument
private Collection<String> someStringArgument;

sämtliche String-Instanzen aus dem Kontext angefordert und in das Feld injiziert.

In Zusammenhang mit Collections existiert eine Einschränkung der InjectedCommands: Es ist nicht möglich, Objekte des Typs Collection im Kontext abzulegen und mittels Argument-Injection auszulesen. Grund hierfür ist die Generic Type Erasure: Zur Laufzeit sind die durch Generics spezifizierten Typinformationen nicht vorhanden, sodass eine korrekte Typkonvertierung nicht zuverlässig möglich ist. Soll eine Ansammlung von Objekten im Kontext abgelegt und injiziert werden, so empfehlen wir dafür ein dediziertes Fachobjekt zu erstellen, das die Objektmenge vorhält.

Wie auch schon bei den Parametern der Fall, ist es auch für Annotationen des Typs @Argument möglich, statt einer Klassenvariable die entsprechende setter-Methode zu annotieren. Auch bezüglich der Java Sichtbarkeits-Schlüsselworte gilt oben gesagtes.

Die Annotation @Argument bietet verschiedene Optionen an, die den Injektions-Vorgang beeinflussen können. Wie es schon für Parameter der Fall war, können auch Argumente als optional markiert werden. Des Weiteren existiert eine Option boolean allowOtherMatches, die festlegt, ob bei der Abfrage nach einem einzelnen Objekt das Vorhandensein mehrerer gleicher Objekttypen zu einem Fehler führen soll. Die Option boolean matchSubclasses legt fest, ob bei der Suche nach Objekten im Kontext auch Subtypen des angeforderten Typs zurückgeliefert werden sollen. Eine letzte Option, Class<? extends Object> match, definiert nach welchem Objekttyp der Kontext durchsucht werden soll. Wie aus dem Beispiel bereits hervorging, wird der gewünschte Typ im Regelfall automatisch erkannt. Dieses zusätzliche Attribut erlaubt es jedoch, eine nähere Einschränkung – zum Beispiel auf eine bestimmte Subklasse – zu treffen.

Eine letzte Möglichkeit der Argument-Injizierung bietet die Annotation @AllArguments. Diese stellt sämtliche Argumente des Kontexts zur Verfügung. Im Regelfall geschieht dies über einen Rückgabewert des Typs Collection<Object>, wobei zu bedenken ist, dass die Klasse Context das Interface Collection implementiert. Man bekommt daher eigentlich eine Instanz von Context zurück. Um an dieser Stelle keine manuellen Typ-Checks und -Konvertierungen durchführen zu müssen, kann auf der Annotation @AllArguments die Option match = Context.class spezifiziert werden. Ist diese Option vorhanden, so wird direkt eine Instanz von Context zurückgegeben.

[jadice® document platform 5 Dokumentation. Erstellungsdatum: 2011-08-12]
loading table of contents...