~/home of geeks

Groovy Scripting in Java

· 441 Wörter · 3 Minute(n) Lesedauer

Neulich musste ich mir was überlegen, mit dem man einzelne Objekte einer Liste Iterieren und Filtern kann, möglichst flexibel und in einer Konfigurationsdatei konfigurierbar. Ein ideales Einsatzgebiet für ein Skript, dass die Filterung vornehmen kann.

Die Situation sah wie folgt aus: Wir haben ein Objekt (eine Bean), die mehrere Attribute hat, z. B. vorname, nachname, alter, postleitzahl (plz) und stadt. Diese werden aus einer Datei geladen und sollen nach bestimmten, in einer Konfiguration definierbaren Kriterien nicht nur gefiltert, sondern sogar bearbeitet werden. Ein Beispiel wäre der folgende Fall:

List<MyBean> beans = ....;
final ArrayList<MyBean> filteredBeans = new ArrayList<>();

for (MyBean bean:beans){
  if (bean.stadt==null && bean.plz=51105){
    bean.stadt="Köln";
  }
  if (bean.alter>=18){
    filteredBeans.add(bean);
  }
}

So in etwa würde ein Codeschnipsel aussehen, der eine solche Liste von Objekten filtert und bearbeitet. Wie die Objekte aber zu bearbeiten sind, sollte je nach Konfigurationsdatei verschieden sein, denn es gab etwa zwei dutzend verschiedene Konfigurationsdateien.

Also, dachte ich mir, wäre es hier doch ideal, statt für jede Konfigurationsdatei auch eine eigene Klasse zu schreiben, in der die spezifische Verarbeitung vorgenommen wird, diese einfach als Skript in die Konfigurationsdatei mit einzubeziehen. Da ich vor einiger Zeit mit Groovy herumgespielt hatte und diese sehr Javanah ist, brauchte ich nicht lange zu suchen.

Groovy bietet u. a. die Möglichkeit, zur Laufzeit ein Groovyskript bzw. eine in Groovy geschriebene Klasse zu kompilieren und in Java zu benutzen.

Damit die Skripte in der Konfigurationsdatei auf ein Minimum reduziert werden konnten (muss man immer die Schleife mit programmieren?) habe ich meine Groovyskripte aus einer in einem String gespeicherten Template-Klasse zusammen gebaut:

public class GroovyProcessor implements MyBeanProcessor{
  private String script;
  private MyBeanProcessor groovy;

  public void setScript(String script){
    this.script = script;
  }

  public List<MyBean> process(List<MyBean> beans){
    if (groovy==null){
      // create new instance

      // template code makes it easier
      final StringBuilder sb = new StringBuilder(512);
      sb.append("import " + getClass().getName() + ";\n");
      sb.append("import " + MyBeanProcessor.class.getName() + ";\n");
      sb.append("public class " + confName + " implements MyBeanProcessor{\n");
      sb.append("    public List process(List<MyBean> beans){\n");
      sb.append("      final ArrayList<MyBean> filteredBeans = new ArrayList<MyBean>();\n");
      sb.append("      for (MyBean bean:beans){\n");
      sb.append("        boolean skip = false;\n");
      sb.append(script + "\n");
      sb.append("        if (!skip){\n");
      sb.append("          filteredBeans.add(bean);\n");
      sb.append("        }\n");
      sb.append("      }\n");
      sb.append("      return filteredBeans;\n");
      sb.append("    }\n");
      sb.append("}");

      GroovyClassLoader groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader());
      Class groovyClass = groovyClassLoader.parseClass(sb.toString());
      groovy = (MyBeanProcessor) groovyClass.newInstance();
    }
    return groovy.process(beans);
  }
}

Mit diesem Codetemplate in Groovy reduziert sich das obige Beispiel zu folgendem Skript:

if (bean.stadt == null && bean.plz == 51105){
  bean.stadt = "Köln";
};
if (bean.alter < 18){
  skip = true;
};

Zu beachten bei der Nutzung von Groovy ist, dass man dem GroovyClassLoader eine “gute” Classloader Instanz gibt. Der System.getDefaultClassLoader() macht Probleme beim Finden von Klassen in den Klassenpfaden eines Webcontainers.