Autorenarchiv

Excel-Dateien mit Apache POI bearbeiten

Montag, 07. Juni 2010

Ein für viele Entwickler eher unerfreuliches Thema ist die Arbeit mit Microsoft Office Dateien in Java. Insbesondere Excel-Dateien werden von Fachabteilungen gerne als Ein- oder Ausgabeformat gesehen. Oft kann man sich dann auf einen Import bzw. Export via csv einigen. Wenn dies nicht möglich ist, lohnt es sich, einen Blick auf Apache POI zu werfen. Das Projekt hat sich der MS-Office-Thematik angenommen und gliedert sich in mehrere Subprojekte, die sich jeweils mit den einzelnen Office-Formaten beschäftigen. Viele dieser Subprojekte sind noch in einem sehr frühen Stadium und daher mit Vorsicht einzusetzen. Als ziemlich ausgereift kann man hingegen die Subprojekte HSSF und XSSF bezeichnen. HSSF liefert Unterstützung für das alte Excel-Dateinformat, das bis einschließlich Excel 2003 eingesetzt wurde. XSSF dagegen liefert die Unterstützung für das neuere XML-basierte Format, das mit Excel 2007 eingeführt wurde. Im folgenden soll anhand eines kurzen Beispiels ein erster Einblick in die Arbeit mit HSSF/XSSF gegeben werden. Dabei wird die Version 3.6 von POI eingesetzt.

HSSF und XSSF arbeiten mit einer Reihe von gemeinsamen Interfaces, die die Bestandteile einer Excel-Datei repräsentieren. Die folgenden Interfaces kommen in unserem Beispiel zum Einsatz:

  • Workbook repräsentiert eine Excel-Datei
  • Sheet repräsentiert eine einzelne Tabelle
  • Row repräsentiert eine einzelne Tabellenzeile
  • Column repräsentiert eine einzelne Tabellenspalte
  • Cell repräsentiert eine einzelne Tabellenzelle

Um nun eine einfache Excel-Tabelle zu erstellen und in einer xls-Datei zu speichern gehen wir wie folgt vor:

Workbook workbook;
File outputFile;
// wir wollen eine Excel 97 xls-Datei erzeugen, verwenden also HSSF
workbook = new HSSFWorkbook();
outputFile = new File("c:/Dateiname.xls");
 
// bei der Erzeugung der Daten arbeiten wir nur noch mit den Interfaces und damit
// formatunabhängig:
Sheet sheet = workbook.createSheet("Einkäufe");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Ware");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Preis");
row = sheet.createRow(1);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Brot");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(1.19);
row = sheet.createRow(2);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Butter");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(0.89);
row = sheet.createRow(3);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Summe");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
// wir können mit setCellFormula auch Formeln für einzelne Zellen angeben
cell.setCellFormula("SUM(B2:B3)");
 
// Schreiben der Datei
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
workbook.write(fileOutputStream);
fileOutputStream.close();

Um stattdessen eine xslx-Datei (Excel 2007) zu erzeugen müssen wir im obigen Beispiel lediglich die Zeilen

workbook = new HSSFWorkbook();
outputFile = new File("c:/Dateiname.xls");

durch

workbook = new XSSFWorkbook();
outputFile = new File("c:/Dateiname.xlsx");

ersetzen. Der restliche Code bleibt unverändert. Ziemlich einfach, oder?

Ich wünsche viel Spaß beim Ausprobieren!


Malte Wulf


Dependency Injection und aspektorientierte Programmierung mit Google Guice (Teil 2)

Freitag, 25. September 2009

Nachem ich im ersten Teil eine kurze Einführung in die Verwendung von Dependency Injection mit Guice gegeben habe, möchte ich im Folgenden zeigen, wie man aspektorientierte Programmierung (AOP) mit Guice einsetzen kann. Auch hier genügt die Kenntnis einiger weniger Klassen und Interfaces um zum Ziel zu gelangen.

Einführung in AOP mit Guice

Grundidee von AOP ist, bestimmte übergreifende Aspekte einer Anwendung (sogenannte Cross-Cutting Concerns) aus den einzelnen Methoden auszugliedern, um so den Code lesbarer zu machen und Wiederholungen zu vermeiden. Beispiele für solche Cross-Cutting Concerns sind zum Beispiel das Logging von Methodenaufrufen oder die Prüfung von erforderlichen Berechtigungen. Diese Aspekte sind zwar notwendig, tragen aber nicht zum Erfüllen der eigentlichen Aufgabe der jeweiligen Methode bei und sollten im Sinne von Separation of Concerns nicht vom eigentlichen Geschehen in der Methode ablenken und daher ausgegliedert werden.

In Google Guice kann die Ausgliederung solcher Belange durch Method Interception umgesetzt werden. Das Prinzip ist folgendes: Methodenaufrufe werden zur Laufzeit abgefangen, untersucht und gegebenenfalls wird ihnen der Code des passenden Aspekts vor- bzw. nachgeschaltet. Was zunächst vielleicht kompliziert klingt, läßt sich mit Guice sehr einfach umsetzen:

  • Um festzustellen, ob einem Methodenaufruf ein bestimmter Aspekt vorangestellt bzw. angehängt wird, gibt es in Guice sogenannte Matcher. Das Interface Matcher sieht eine Methode matches vor, die als Parameter ein Objekt vom Typ T erhält und für dieses entscheidet, ob es von diesem Matcher akzeptiert wird oder nicht. Matcher erzeugt man am einfachsten über die Factory-Klasse Matchers.
  • Für die Logik des auszugliedernden Aspekts implementiert man das Interface MethodInterceptor. In dessen invoke-Methode kann man dann festlegen, was vor bzw. nach dem Aufruf der Methode geschehen soll.
  • Die im ersten Teil vorgestellte Klasse AbstractModule bietet dann die Möglichkeit, den Interceptor einzubinden. Hierfür verwendet man die Methode bindInterceptor. Diese erhält zwei Matcher und eine beliebige Anzahl von einzubindenden Method Interceptors. Der erste Matcher legt fest, welche Klassen betroffen sind, der zweite Matcher legt die betroffenen Methoden fest.

Beispiel

Das ganze soll nun anhand des Loggings von Methodenaufrufen und Rückgabewerten veranschaulicht werden. Für einfachere Fehlersuche werden Methoden häufig wie folgt “verziert”:

public Integer eineMethode (final Integer arg1, final Integer arg2) {
  if (LOGGER.isDebugEnabled() {
    LOGGER.debug("Aufruf von eineMethode mit Parametern " + arg1 + " " + arg2);
  }
 
  // (...) hier steht der eigentlich relevante Code der Methode
 
  if (LOGGER.isDebugEnabled() {
    LOGGER.debug("Methode eineMethode beendet. Ergebnis: " + returnValue );
  }
}

Dieser Code für das Logging wiederholt sich häufig und bläht die Methoden unnötig auf. Um diese Wiederholungen zu vermeiden und den Code übersichtlicher zu gestalten soll dieses Logging nun ausgegliedert werden.

  • Wir schreiben zunächst eine Klasse MethodCallLogger, die MethodInterceptor implementiert:
    package util.logging;
     
    import java.lang.reflect.Method;
    import java.util.Arrays;
     
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
     
    public class MethodCallLogger implements MethodInterceptor {
     
      @Override
      public Object invoke(final MethodInvocation call) throws Throwable {
        // vor dem Aufruf: Parameter loggen
        Method method = call.getMethod();
        Object[] args = call.getArguments();
        StringBuilder loggingMessageBuilder = new StringBuilder();
        loggingMessageBuilder.append("Aufruf von ");
        loggingMessageBuilder.append(method.getName());
        loggingMessageBuilder.append(" mit Parametern ");
        loggingMessageBuilder.append(Arrays.deepToString(args));
        System.out.println(loggingMessageBuilder.toString());
        // eigentlichen Aufruf durchführen
        Object result = call.proceed();
        // nach dem Aufruf: Ergebnis loggen
        loggingMessageBuilder = new StringBuilder();
        loggingMessageBuilder.append(method.getName());
        loggingMessageBuilder.append(" beendet. Ergebnis ");
        loggingMessageBuilder.append( result.toString());
        System.out.println(loggingMessageBuilder.toString());
        return result;
      }
    }
  • In unserem Modul legen wir nun fest, an welche Klassen und Methoden wir den Interceptor binden. Wir machen es uns einfach und verwenden für beide Matcher Matchers.any():
    package aop;
     
    import util.logging.MethodCallLogger;
    import com.google.inject.AbstractModule;
    import com.google.inject.matcher.Matchers;
     
    public class AOPModule extends AbstractModule {
      @Override
      protected void configure() {
        bindInterceptor(Matchers.any(), Matchers.any(), new MethodCallLogger());
      }
    }

Das war alles: Die Methoden aller Klassen, die wir mit Hilfe unseres AOPModule instanzieren, werden nun automatisch von unserem MethodCallLogger umschlossen.

Man kann das Verfahren natürlich weiter verfeinern. Beispielsweise könnte man eine Annotation @MethodCallLogging schreiben, mit der man dann manuell festlegt, welche Methodenaufrufe geloggt werden sollen. Diese Annotation kann dann mit einem passenden Matcher abgefragt werden, den man mittels Matchers.annotatedWith() erhält.

Weitere Informationen findet man auf der Projektseite und in den JavaDocs.

Viel Spaß beim Ausprobieren!


Malte Wulf


Dependency Injection und aspektorientierte Programmierung mit Google Guice (Teil 1)

Montag, 07. September 2009

Bei den Stichworten aspektorientierte Programmierung (AOP) und Dependency Injection (DI) fällt dem informierten Entwickler natürlich sofort das Spring Framework ein. Weitaus weniger bekannt dürfte den meisten das Guice Framework von Google sein, welches diese Techniken ebenfalls zur Verfügung stellt und mittlerweile in Version 2.0 vorliegt. Während es in Googles eigenen Anwendungen intensiv genutzt wird, ist seine Verbreitung insgesamt noch eher gering.

Vergleich mit Spring

Aufgrund zahlreicher Unterschiede dürfte sich das Framework aber in einigen Fällen als interessante Alternative zu Spring erweisen. Zum Beispiel geschieht das Wiring der Typen mit ihren konkreten Implementierungen nicht wie in Spring über eine XML-Konfiguration, sondern wird direkt über Java-Code mit Annotationen realisiert. Dadurch läßt es sich auch ohne spezielle IDE-Plugins einfach im Java-Editor schreiben. Das ganze hat allerdings den nicht zu unterschätzenden Nachteil, dass eine Änderung am Wiring ein erneutes Compilieren erfordert und dass Guice auch fester im eigentlichen Code verankert wird als dies beim Spring Framework der Fall ist – dies macht sich vor allem dann bemerkbar, wenn man in einem laufenden Projekt von Guice auf ein anderes Framework wechseln möchte.
Ein weiterer wesentlicher Unterschied liegt im Umfang der Frameworks. Guice ist recht deutlich auf die Techniken AOP und DI reduziert, während Spring als “Schweizer Taschenmesser” unter den Frameworks betrachtet werden kann, das nahezu alle Entwicklerbedürfnisse abdeckt. Je nach Situation kann dies als Segen oder Last empfunden werden – der schnellere und wohl auch einfachere Einstieg ist meines Erachtens aber mit Guice möglich. Beim Thema “einfacher Einstieg” sollte allerdings nicht unerwähnt bleiben, dass es über Spring nicht zuletzt aufgrund der hohen Bekanntheit und Verbreitung zahlreiche Bücher, Artikel und Tutorials gibt, während man bei Guice deutlich weniger Quellen zur Auswahl hat.

Einführung in Dependency Injection mit Guice

Zunächst  möchte ich einen Überblick über die für Dependency Injection benötigten Bestandteile des Frameworks geben:

  • Mit der @Inject Annotation werden Members einer Klasse annotiert, in die von Guice – genau gesagt vom Injector – Werte injiziert werden sollen. Dies können beispielsweise Felder oder Konstruktoren sein. Bei Konstruktoren ist zu beachten, dass höchstens ein Konstruktor derart annotiert werden darf. Ist kein Konstruktor mit @Inject markiert, so verwendet Guice den Standardkonstruktor um Objekte der Klasse zu erzeugen.
  • Ein Injector wird verwendet, um Objekte zu erzeugen. Für die Erzeugung von Objekten bietet das Injector-Interface die Methode getInstance an, die als Argument die gewünschte Klasse erhält und dann ein passendes Objekt zurückliefert. Den Injector selbst erhält man über den Aufruf der createInjector-Methode der Klasse Guice. Diese erhält als Argument ein oder mehr Module-Objekte, in denen das Wiring definiert ist.
  • Die Module-Objekte enthalten das Wiring in Form sogenannter Bindings. Mit Bindings werden also Typen auf konkrete Implementierungen abgebildet. Am einfachsten läßt sich ein Module schreiben, indem man von der Klasse AbstractModule ableitet und deren configure-Methode überschreibt. In der configure-Methode wird z.B. mit der Zeile
    bind( KlasseA.class ).to ( KlasseB.class );

    festgelegt, dass durch Guice überall dort, wo der Injector ein Objekt vom Typ KlasseA benötigt, tatsächlich ein KlasseB-Objekt eingesetzt werden soll.

Beispiel

Angenommen wir haben ein Interface BeispielInterface und eine Klasse KomplexesObjekt, die dieses implementiert. Zum Testen wollen wir statt KomplexesObjekt aber lieber ein Objekt der Klasse MockObjekt verwenden, die ebenfalls das BeispielInterface implementiert. Mit Guice läßt sich dies wie folgt umsetzen:

  1. Die Konstruktoren von KomplexesObjekt und MockObjekt werden ggf. mit @Inject annotiert.
  2. Module für das Wiring im Produktiv- bzw. Testeinsatz werden geschrieben. Das Test-Module könnte z.B. so aussehen:
    import beispiel.BeispielInterface;
    import com.google.inject.AbstractModule;
     
    /**
     * Module für den Einsatz des MockObjekts
     */
    public class MockModule extends AbstractModule {
        @Override
        protected void configure() {
            bind(BeispielInterface.class).to(MockObjekt.class);
        }
    }
  3. Die Objekte werden durch den Injector erzeugt:
    BeispielInterface beispielObjekt;
    Injector testInjector = Guice.createInjector(new MockModule());
    beispielObjekt = testInjector.getInstance(BeispielInterface.class);

Mehr ist für dieses einfache Beispiel nicht zu tun. So unkompliziert kann Dependency Injection sein. Natürlich bietet auch Guice noch eine Fülle weiterer Möglichkeiten an. Weitere Informationen findet man hauptsächlich auf der Projektseite .

Im nächsten Teil werde ich dann einen kurzen Blick auf AOP mit Guice wagen.
Bis dahin wünsche ich viel Spaß beim Ausprobieren!


Malte Wulf


Vorsicht im Umgang mit SortedSet

Mittwoch, 24. Juni 2009

Sortierte Mengen werden häufig als Implementierungen des Interfaces SortedSet umgesetzt. Dieses Interface sieht vor, dass die Elemente einer solchen Menge entweder gemäß ihrer natürlichen Ordnung (durch die compareTo-Methode von Comparable definiert) oder durch einen Comparator sortiert werden. Manch einer übersieht dabei aber eine wichtige Voraussetzung an die compareTo- bzw. compare-Methode: Sie muß mit der equals-Methode konsistent sein, d.h. für zwei Objekte a und b muß a.equals(b) genau dann true zurückliefern, wenn a.compareTo(b) (bzw. die compare-Methode des Comparators) den Wert 0 liefert. Wenn dies nicht der Fall ist, verhält sich die Collection möglicherweise nicht mehr wie eine Menge, d.h. es ist nicht mehr sichergestellt, dass

  1. es keine zwei (im Sinne von equals) gleichen Elemente in der Menge gibt.
  2. man ein Element, das sich (im Sinne von equals) von allen Elemten innerhalb der Collection unterscheidet, einfügen kann.

In diesem Fall wäre man mit einem gewöhnlichen Set, das man selbst mit Hilfe von Collections.sort() sortiert, besser beraten. Ein kleines Beispiel soll das Problem verdeutlichen:
Die Klasse Person überschreibt equals derart, dass Personen mit gleicher id als gleich betrachtet werden:

public class Person {
  private Integer id;
  private Integer age;
  public Integer getId() {
      return id;
  }
  public void setId(final Integer id) {
      this.id = id;
  }
  public Integer getAge() {
      return age;
  }
  public void setAge(final Integer age) {
    this.age = age;
  }
 
  @Override
  public boolean equals(final Object obj) {
    if (obj instanceof Person && id==(((Person)obj).id))
      return true;
    else
      return false;
  }
 
  public Person(final Integer id, final Integer age) {
    this.id = id;
    this.age = age;
  }
}

Wir definieren nun einen Comparator, der Personen nach ihrem Alter vergleicht und legen mit dessen Hilfe ein SortedSet an, in das wir zwei verschiedene Personen einfügen:

import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
 
public class Example {
 
  public static void main(final String[] args) {
    Comparator comp = new Comparator() {
      @Override
      public int compare( Person o1, Person o2) {
        return o1.getAge().compareTo(o2.getAge());
      }
    };
    SortedSet exampleSet =
      new TreeSet(comp);
    exampleSet.add(new Person(1,25));
    exampleSet.add(new Person(2,25));
    System.out.println("Size: "+exampleSet.size());
  }
}

Die Ausgabe bestätigt: es wurde lediglich eine Person tatsächlich in die Menge aufgenommen, obwohl die Personen anhand der equals-Methode eindeutig unterschieden werden. Bei komplexeren Comparator-Klassen kann es durchaus sinnvoll sein, die Konsistenz von equals-Methode und Comparator mittels eines eigenen Tests sicherzustellen. Aber bereits ein sorgfältiger Blick zur rechten Zeit kann einen hier vor schwer auffindbaren Fehlern bewahren.


Malte Wulf