Java Reflections: Dynamisches Erstellen von Objekten

Veröffentlicht am 12. June 2010

Ich arbeite im Moment an dem interessanten Projekt, einen Traffic-Generator zu schreiben.

Dabei sollen die Zwischenankunftszeiten und Paketgrößen nach wählbaren Verteilungsfunktionen mit frei wählbaren Parametern verteilt sein.

Ich verwende die Java-Bibliothek JSimLib (eine Eigenentwicklung des Lehrstuhls), um Zufallszahlen nach bestimmten Verteilungen zu erhalten.

Da es einige Verteilungsklassen gibt (und diese möglicherweise noch ergänzt werden), und diese Verteilungsklassen unterschiedlich viele Parameter akzeptieren, wird ein Java-Machanismus benötigt, der ein Verteilungsobjekt mit der richtigen Verteilungklasse und den richtigen Parametern aus den Argumenten der Java-Kommandozeilenargumente erstellt.

Hierzu verwende ich die Reflections-Klassen von Java, die es ermöglichen, zur Laufzeit Klassen und Objekte zu untersuchen und zu erstellen.

Die einzelnen Schritte des Ansatzes:

  • Hole den Namen der Verteilungsklasse aus den Argumenten und überprüfe, ob es sich dabei um ein gültige Verteilungsklasse handelt
  • Finde einen Konstruktor dieser Klasse, der mehrere Double-Agrumente erwartet
  • Rufe den Konstruktor mit den Argumenten aus der Java-Kommandozeile auf und gib das Verteilungsobjekt zurück

Für den ersten Schritt muss eine Klasse mit dem entsprechenden Namen gesucht werden (alle Verteilungsklassen liegen in einem speziellen Paket), die kompatibel mit der Klasse RandomStream ist, und nicht abstrakt ist.

public static final String DistributionPrefix = "com.jSimLib.distributions.";
  
  public static boolean isDistribution(String classname) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      if (!RandomStream.class.isAssignableFrom(clazz)) return false;
      if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0) return false;
      return true;
    } catch (ClassNotFoundException e) {
      return false;
    }
  }

Als nächstes wird ein Konstruktor dieser Klasse gesucht, der nur double-Argumente erwartet:

public static Class<?>[] getConstructorArguments(String classname) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      
      Constructor<?>[] constructors = clazz.getConstructors();
      for (Constructor<?> c : constructors) {
        Class<?>[] args = c.getParameterTypes();
        if (args.length == 0) continue;
        for (Class<?> arg : args) {
          if (!(arg.getName().equals("double") || arg.getName().equals("java.lang.Double"))) continue;
        }
        return args;
      }
      throw new IllegalArgumentException("Distribution class " + classname + " doesn't have a valid non-empty constructor.");
      
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("Argument must be a valid RandomStream classname.");
    }
    
  }

Um den Konstruktor dann tatsächlich aufzurufen und ein Objekt der Klasse zu erhalten, kann folgende Methode benutzt werden:

public static RandomStream buildDistribution(String classname, Class<?>[] argtypes, Object[] args) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      
      Constructor<?> constructor = clazz.getConstructor(argtypes);
      return (RandomStream) constructor.newInstance(args);
      
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("Argument must be a valid RandomStream classname.");
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " doesn't have a valid constructor with " + args.length + " double arguments.");
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (InvocationTargetException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (ClassCastException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    }
  }

Insgesamt kann nun also mit diesen drei statischen Methoden ein Objekt einer beliebigen Verteilungsklasse erstellt werden.

Um also beispielsweise ein Objekt der Normalverteilung mit den Parametern 5 und 0.5 zu erhalten, können die Methoden folgendermaßen aufgerufen werden:

String classname = "NormalStream";
// Mean: 5.0, Standard deviation: 0.5
double[] args = { 5.0, 0.5 };

if (DistBuilder.isDistribution(classname)) {
  Class<?>[] signature = DistBuilder.getConstructorArguments(classname);
  if (signature.legth == 2) { // Yes, it is ;-)
    try {
      RandomStream dist = DistBuilder.buildDistribution(classname, signature, args);
      // Get random numbers
      // ...
    } catch (IllegalArgumentException e) {
     e.printStackTrace();
    }
  }
}

Natürlich wird das Verfahren erst interessant, wenn Klassenname und Argumente zur Compilezeit nicht feststehen und dynamisch erzeugt werden.

Aber das überlasse ich den Programierkünsten des Lesers …

package de.uniwue.info3.p2p.netfpga.pcapgen;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

import com.jSimLib.distributions.RandomStream;

/**
 * The class DistBuilder ist a utility class to construct distribution objects
 * from a classname and an array or arguments.
 * @author Christian Simon
 * @version 1.0
 */

public class DistBuilder {

  public static final String DistributionPrefix = "com.jSimLib.distributions.";
  
  public static boolean isDistribution(String classname) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      if (!RandomStream.class.isAssignableFrom(clazz)) return false;
      if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0) return false;
      return true;
    } catch (ClassNotFoundException e) {
      return false;
    }
  }
  
  public static Class<?>[] getConstructorArguments(String classname) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      
      Constructor<?>[] constructors = clazz.getConstructors();
      for (Constructor<?> c : constructors) {
        Class<?>[] args = c.getParameterTypes();
        if (args.length == 0) continue;
        for (Class<?> arg : args) {
          if (!(arg.getName().equals("double") || arg.getName().equals("java.lang.Double"))) continue;
        }
        return args;
      }
      throw new IllegalArgumentException("Distribution class " + classname + " doesn't have a valid non-empty constructor.");
      
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("Argument must be a valid RandomStream classname.");
    }
    
  }
  
  public static RandomStream buildDistribution(String classname, Class<?>[] argtypes, Object[] args) {
    try {
      Class<?> clazz = Class.forName(DistributionPrefix + classname);
      
      Constructor<?> constructor = clazz.getConstructor(argtypes);
      return (RandomStream) constructor.newInstance(args);
      
    } catch (ClassNotFoundException e) {
      throw new IllegalArgumentException("Argument must be a valid RandomStream classname.");
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " doesn't have a valid constructor with " + args.length + " double arguments.");
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (InvocationTargetException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    } catch (ClassCastException e) {
      throw new IllegalArgumentException("Distribution class " + classname + " could not be constructed:", e);
    }
  }
  
}
Kategorie: Java