Die Standard-Templatesprache zur Generierung von Webseiten mit Java bilden die
Java Server Pages (JSP). Eine gute Alternative zu JSPs ist die Templatesprache Freemarker, mit der sich eine striktere Trennung zwischen Anwendungslogik und View durchsetzen lässt. Wie die Integration von verschiedenen Templatesprachen mit Spring-MVC erfolgt, habe ich in diesem Artikel beschrieben: Velocity, Freemarker, Jade4J – Alternativen zu JSPs
Beim Einsatz von Freemarker muss man darauf achten, dass alle Modell-Werte korrekt gefüllt sind, damit die Platzhalter im Template korrekt ersetzt werden können. Falls ein Wert nicht vorhanden ist, wird mit der Freemarker-Standardkonfiguration Folgendes passieren:
- Freemarker wirft eine Exception:
freemarker.core.InvalidReferenceException
- die Fehlermeldung wird geloggt
- das weitere Generieren der Seite wird abgebrochen
- die Webseite zeigt dann eventuell eine halb fertig generierte Seite oder einen Stacktrace der Exception an
Das ist zwar während der Entwicklung ganz praktisch, aber die Anwender sollten natürliche keine fehlerhafte Seite sehen. Zur Vermeidung von Fehlermeldungen, wenn ein Wert nicht gesetzt ist, können wir folgendes tun:
- Default-Werte im Template definieren
- einen anderen Freemarker-Exception-Handler verwenden
Für die erste Möglichkeit müssen wir viele Stellen im Template anpassen, was umständlich und fehleranfällig ist. Beispielsweise geht das Setzen von Default-Werten im Template seit Version 2.3.7 mit ${address.city!""}
oder für vorherige Versionen mit ${address.city?default("")}
.
Eine umfassendere Lösung bildet die zweite Möglichkeit. Wir brauchen lediglich einen anderen Freemarker-Exception-Handler einzusetzen und können dann innerhalb des Exception-Handlers flexibel auf die Ausnahmesituation reagieren. Freemarker ist mit dem TemplateExceptionHandler.DEBUG_HANDLER
vorkonfiguriert und bricht damit nach dem ersten Fehler ab. Außerdem werden noch drei weitere Exception-Handlers mitgeliefert:
- HTML_DEBUG_HANDLER wie DEBUG_HANDLER, aber generiert Meldung als HTML
- RETHROW_HANDLER loggt die Exception und wirft die Exception weiter
- IGNORE_HANDLER loggt die Exception und ignoriert sie
Für den produktiven Einsatz empfiehlt sich der IGNORE_HANDLER, weil damit die gesamte Seite trotz fehlender Werte ohne Fehlermeldungen generiert wird. In dem folgenden kleinen Beispiel wird im Java-Quellcode der IGNORE_HANDLER eingesetzt.
Java-Code: FreemarkerExceptionExample.java
package de.bruns.example.freemarker.exception; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; public class FreemarkerExceptionExample { public static void main(String[] args) throws IOException, TemplateException { Configuration configuration = new Configuration(); configuration.setClassForTemplateLoading(FreemarkerExceptionExample.class, ""); configuration.setObjectWrapper(new DefaultObjectWrapper()); configuration.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER); Map<String, Object> addressModel = new HashMap<String, Object>(); addressModel.put("country", "USA"); addressModel.put("city", "Springfield"); Map<String, Object> personModel = new HashMap<String, Object>(); personModel.put("name", "Homer Simpson"); // personModel.put("address", addressModel); Template temp = configuration.getTemplate("template.ftl"); Writer out = new OutputStreamWriter(System.out); temp.process(personModel, out); out.flush(); } }
Freemarker-Template: template.ftl
<html> <body> <h1>Freemarker Exception Beispiel</h1> <p> ${name} lebt in ${address.city} (${address.country}). </p> </body> </html>
Ausgabe: TemplateExceptionHandler.IGNORE_HANDLER
<html> <body> <h1>Freemarker Exception Beispiel</h1> <p> Homer Simpson lebt in (). </p> </body> </html>
Besonders flexibel können wir mit einem eigenen Exception-Handler reagieren. Also einfach die einzige Methode des Interfaces TemplateExceptionHandler.handleTemplateException(...)
implementieren und die Freemarker-Konfiguration entsprechend anpassen (Quellcode: https://github.com/me4bruno/blog-examples -> example-freemarker-exception):
Java-Code: TemplateExceptionHandler.java
TemplateExceptionHandler exceptionHandler = new TemplateExceptionHandler() { public void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException { try { out.write("[Missing Value]"); } catch (IOException e) { throw new TemplateException("Failed to print error message. Cause: " + e, env); } } }; Configuration configuration = new Configuration(); configuration.setTemplateExceptionHandler(exceptionHandler);
Ausgabe: TemplateExceptionHandler
<html> <head> <title>Freemarker Exception Beispiel</title> </head> <body> <h1>Freemarker Exception Beispiel</h1> <p> Homer Simpson lebt in [Missing Value] ([Missing Value]). </p> </body> </html>
Wenn wir Freemarker mit Spring MVC einsetzen, können wir den Exception-Handler deklarativ in einer XML-Datei deklarieren. Dazu können wir beispielsweise die Methode FreeMarkerConfigurer.setFreemarkerSettings(Properties settings)
nutzen und mit dem Key-Value Paar "template_exception_handler" => "rethrow", "debug", "html_debug" oder "ignore"
einen Exception-Handler auswählen. Die genauen Parameter erläutert der Javadoc-Kommentar zu der folgenden Methode:
freemarker.core.Configurable.setSetting(String key, String value)
Juhu – keine freemarker.core.InvalidReferenceException
s mehr 😉