Reflection und Annotationen

Reflection-API

Reflection *

Folgende Nachricht wurde in der JDC Tech Tips No. 5 versendet. Lies den Text und implementiere das Beispielprogramm. Teste es an der Klasse java.math.BigDecimal. Wofür kann man die Technik verwenden? Schreibe ein Programm, welches zu einer geladenen Klasse eine Klassenbeschreibung liefert, wie sie im Quellcode vorliegen könnte. Zu der Beschreibung gehören also Konstruktor, Variablen und Methoden.

Komponenten und Klassen *

Eine Firma möchte Komponenten implementieren und auch anderen Anbietern die Möglichkeit geben, Komponenten zu entwickeln. Da das Einfügen neuer Komponenten möglichst einfach sein soll, sollen sie in ein Verzeichnis gestellt werden. Wird die Software neu gestartet, so soll sie automatisch die neuen Komponenten erkennen und einbinden.

Klasse Class **

Möchte man von einer gegebenen Klasse die Vererbungshierarchie auflisten, so ist dies kein großes Problem. (Wieso?) Es gibt aber auch Situationen im Leben eines Programmierers, in denen er alle direkten erbenden Klassen erfahren möchte. Wo liegen aber dort die Schwierigkeiten?

Allerdings lassen sich in einem absoluten Sonderfall Unterklassen herausfinden. Warum funktioniert für welchen Spezialfall das folgende Programmstück:

Class<?> superC = Point2D.class;
Class[] refs = superC.getClasses();

for ( int i = 0; i < refs.length; i++ )
if ( superC != refs[i] && superC.isAssignableFrom( refs[i] ) )
System.out.println( refs[i] + " is subclass of " + superC );

Teste das Programmsegment.

Implementierung *

Wir wollen als konkretes Beispiel einen Taschenrechner einsetzen, der um mathematische Funktionen erweitert werden soll. Definiere eine Schnittstelle für das Problem. Das kann etwa so aussehen:

interface MathOp
{
  String getName();
  double op( double d1, double d2 );
}

Dann kann eine konkrete implementierende Klasse, die eine Funktion implementiert, Folgendes schreiben:

class Sin implements MathOp
{
  String getName()
  {
    return "sin";
  }
  double op( double d1, double d2 )
  {
    return Math.sin( d1 );
  }
}

Schreibe eine Testklasse, die die mathematische Funktion überprüft. Das kann etwas so aussehen:

public static void main( String args[] )
{
  Sin sin = new Sin();
  // Parameter aufbauen
  System.out.println( sin.op( /* Parameter einsetzen */  ) );
}

Kopiere die eigenen Klassen in die Freigabe TNProjekte auf dem Referenten-PC.
Kopiere anschließend alle mathematischen Klassen in das eigene Verzeichnis.

Die java.io.File-Klasse *

Man kann mit der Methode list() von einem java.io.File eine Auflistung aller Dateien im Verzeichnis erhalten.

Klassen dynamisch laden *

Man kann Klassen mit forName() nachladen, wenn man den Klassennamen kennt. Mit newInstance() kann man dann ein Exemplar der Klasse erzeugen. Ein Beispiel:

Class clazz = Class.forName("java.util.Date");
Object o = clazz.newInstance();

Lade alle mathematischen Klassen im Verzeichnis und erzeuge ein Objekt von ihnen mit Hilfe der newInstance()-Methode. Mit toString() kann man erkennen, ob alles gut ging. Fange die entsprechenden Ausnahmen ab.

Übergebe dazu alle gefundenen Klassen von list() der Methode klasseLaden(), die das Objekt vom Typ MathOp zurückliefert.

Annotationen

Annotationen als Modifizierer

Wenn eine Annotation ein Modifizierer wie public, static oder final ist, welche Konsequenz hat das?

Markierungsannotationen

Mit EJB 3 ist es möglich, ohne XML-Konfigurationsdateien (aka Deployment-Deskriptor) Enterprise JavaBeans zu schreiben. Ein Beispiel:

// import javax.ejb.Stateless;
// import javax.ejb.Remote;

@Remote
@Stateless
public class CalculatorBean // implements Calculator
{
  public int add( int x, int y )
  {
    return x + y;
  }

  public int subtract( int x, int y )
  {
    return x - y;
  }
}

Definiere die dafür nötigen Annotations-Typen.

Elemente für Annotationen

Der Annotations-Typ @Stateless soll um Elemente erweitert werden.

  1. Definiere ein Element name vom Typ String. Es soll standardmäßig auf "" stehen.
  2. Definiere eine neue Aufzählung TransactionManagementType (nicht im Inneren von @Stateless), mit den Elementen CONTAINER und BEAN. @Stateless soll ein Element transactionManagement bekommen, welches standardmäßig auf CONTAINER steht.

Erzeuge eine neue Annotation @Stateful. Kopiere die Elemente aus @Stateless, denn es sind die selben!

@Stateless/@Stateful annotieren

  1. Welcher @Target ist bei @Stateless/@Stateful sinnvoll?
  2. Erzeuge eine neue Annotation @Remove, welches nur auf Methoden angewendet werden darf. Es soll ein Element boolean retainIfException() default false; definieren.
  3. Siehe in der EJB 3 Spezifikation im Kapitel "10.1.1/10.1.2 Stateless/Stateful Session Beans" nach, ob und wie @Retention gesetzt ist.

Zugriff auf die Annotationen

Teste, ob der Typ CalculatorBean eine Annotation trägt. Erfrage, welche Annotationen mit den Klassen verbunden sind.

Annotationen für Testfälle (15 Minuten)

Was macht die folgende Klasse? Bringe das Beispiel mit einem neuen Annotations-Typ @Test zum Laufen.

import java.lang.reflect.Method;

public class TestRunner
{
 static void executeTests( String className )
 {
  try
  {
   executeTests( Class.forName(className) );
  }
  catch ( ClassNotFoundException e )
  {
   e.printStackTrace();
  }
 }

 static void executeTests( Class clazz )
 {
  try
  {
   Object testObject = clazz.newInstance();
   Method[] methods = testObject.getClass().getDeclaredMethods();

   for ( Method amethod : methods )
   {
    Test testAnnotation = amethod.getAnnotation( Test.class );
    if ( testAnnotation != null )
    {
     System.out.print( testAnnotation.value() + " : " );
     String result = invoke( amethod, testObject );
     System.out.println( result );
    }
   }
  }
  catch ( Exception e )
  {
   e.printStackTrace();
  }
 }

 static String invoke( Method m, Object o )
 {
  try
  {
   m.invoke( o, (Object[]) null );
   return "passed";
  }
  catch ( Exception x )
  {
   return "failed";
  }
 }

 public static void main( String[] args )
 {
  executeTests( "ClassToTest" );
//  executeTests( ClassToTest.class );
 }
}